pax_global_header00006660000000000000000000000064146637544610014532gustar00rootroot0000000000000052 comment=4700ad6f1bef13e411772d7ae4399f891fc3a6ae notation-1.2.0/000077500000000000000000000000001466375446100133655ustar00rootroot00000000000000notation-1.2.0/.dev.goreleaser.yml000066400000000000000000000025611466375446100170770ustar00rootroot00000000000000# Copyright The Notary Project Authors. # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. builds: - main: ./cmd/notation id: notation binary: notation env: - CGO_ENABLED=0 goos: - linux - darwin - windows goarch: - amd64 - arm64 - arm goarm: - '7' ignore: - goos: windows goarch: arm64 - goos: windows goarch: arm - goos: darwin goarch: arm ldflags: - -s -w -X {{.ModulePath}}/internal/version.Version={{.Version}} -X {{.ModulePath}}/internal/version.GitCommit={{.FullCommit}} -X {{.ModulePath}}/internal/version.BuildMetadata= archives: - format: tar.gz format_overrides: - goos: windows format: zip files: - LICENSE release: prerelease: true header: | ## Notation Weekly Dev Build ({{ .Date }}) Welcome to this Weekly Dev Build!notation-1.2.0/.editorconfig000066400000000000000000000016541466375446100160500ustar00rootroot00000000000000# Copyright The Notary Project Authors. # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. root = true [*] charset = utf-8 trim_trailing_whitespace = true insert_final_newline = true end_of_line = lf indent_style = space indent_size = 2 tab_width = 2 [*.go] indent_size = 4 tab_width = 4 indent_style = tab # required for multiline strings in test cases trim_trailing_whitespace = false [Makefile] indent_size = 4 tab_width = 4 indent_style = tab notation-1.2.0/.github/000077500000000000000000000000001466375446100147255ustar00rootroot00000000000000notation-1.2.0/.github/.codecov.yml000066400000000000000000000012101466375446100171420ustar00rootroot00000000000000# Copyright The Notary Project Authors. # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. coverage: status: project: default: target: 70%notation-1.2.0/.github/ISSUE_TEMPLATE/000077500000000000000000000000001466375446100171105ustar00rootroot00000000000000notation-1.2.0/.github/ISSUE_TEMPLATE/bug-or-issue.yaml000066400000000000000000000051231466375446100223160ustar00rootroot00000000000000# Copyright The Notary Project Authors. # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. name: 🐛 Bug or Issue description: Something is not working as expected or not working at all! Report it here! labels: [bug, triage] body: - type: markdown attributes: value: | Thank you for taking the time to fill out this issue report. 🛑 Please check existing issues first before continuing: https://github.com/notaryproject/notation/issues - type: textarea id: verbatim validations: required: true attributes: label: "What is not working as expected?" description: "In your own words, describe what the issue is." - type: textarea id: expect validations: required: true attributes: label: "What did you expect to happen?" description: "A clear and concise description of what you expected to happen." - type: textarea id: reproduce validations: required: true attributes: label: "How can we reproduce it?" description: "Detailed steps to reproduce the behavior. Commands and their outputs are always helpful. If the bug is in a library, code snippets work as well." - type: textarea id: environment validations: required: true attributes: label: Describe your environment description: "Notation installation method (e.g. wget, curl, brew, apt-get, yum, chocolate, MSI) if applicable / OS version / Shell type (e.g. zsh, bash, cmd.exe, Bash on Windows) / Golang version if applicable" - type: textarea id: version validations: required: true attributes: label: What is the version of your Notation CLI or Notation Library? description: "For the CLI, you can use the command `notation version` to get it. For the libraries check the `go.mod` file." - type: markdown attributes: value: | If you want to contribute to this project, we will be happy to guide you through out contribution process especially when you already have a good proposal or understanding of how to fix this issue. Join us at https://slack.cncf.io/ and choose #notary-project channel. notation-1.2.0/.github/ISSUE_TEMPLATE/config.yml000066400000000000000000000013411466375446100210770ustar00rootroot00000000000000# Copyright The Notary Project Authors. # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. blank_issues_enabled: false contact_links: - name: Ask a question url: https://slack.cncf.io/ about: "Join #notary-project channel on CNCF Slack" notation-1.2.0/.github/ISSUE_TEMPLATE/feature-request.yaml000066400000000000000000000041111466375446100231120ustar00rootroot00000000000000# Copyright The Notary Project Authors. # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. name: 🚀 Feature Request description: Suggest an idea for this project. labels: [enhancement, triage] body: - type: markdown attributes: value: | Thank you for taking the time to suggest a useful feature for the project! - type: textarea id: problem validations: required: true attributes: label: "Is your feature request related to a problem?" description: "A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]" - type: textarea id: solution validations: required: true attributes: label: "What solution do you propose?" description: "A clear and concise description of what you want to happen." - type: textarea id: alternatives validations: required: true attributes: label: "What alternatives have you considered?" description: "A clear and concise description of any alternative solutions or features you've considered." - type: textarea id: context validations: required: false attributes: label: "Any additional context?" description: "Add any other context or screenshots about the feature request here." - type: markdown attributes: value: | If you want to contribute to this project, we will be happy to guide you through out contribution process especially when you already have a good proposal or understanding of how to improve the functionality. Join us at https://slack.cncf.io/ and choose #notary-project channel. notation-1.2.0/.github/dependabot.yml000066400000000000000000000023611466375446100175570ustar00rootroot00000000000000# Copyright The Notary Project Authors. # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # To get started with Dependabot version updates, you'll need to specify which # package ecosystems to update and where the package manifests are located. # Please see the documentation for all configuration options: # https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates version: 2 updates: - package-ecosystem: "gomod" # See documentation for possible values directory: "/" # Location of package manifests schedule: interval: "weekly" - package-ecosystem: "github-actions" # Workflow files stored in the # default location of `.github/workflows` directory: "/" schedule: interval: "weekly" notation-1.2.0/.github/licenserc.yml000066400000000000000000000026571466375446100174310ustar00rootroot00000000000000# Copyright The Notary Project Authors. # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. header: license: spdx-id: Apache-2.0 content: | Copyright The Notary Project Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. paths-ignore: - '**/*.md' - 'CODEOWNERS' - 'LICENSE' - 'MAINTAINERS' - 'go.mod' - 'go.sum' - '**/testdata/**' comment: on-failure dependency: files: - go.modnotation-1.2.0/.github/workflows/000077500000000000000000000000001466375446100167625ustar00rootroot00000000000000notation-1.2.0/.github/workflows/add-to-project.yml000066400000000000000000000017221466375446100223230ustar00rootroot00000000000000# Copyright The Notary Project Authors. # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. on: issues: types: - opened permissions: contents: read jobs: add-to-project: name: Add issue to project runs-on: ubuntu-latest steps: - uses: actions/add-to-project@244f685bbc3b7adfa8466e08b698b5577571133e # v1.0.2 with: project-url: https://github.com/orgs/notaryproject/projects/10 github-token: ${{ secrets.ADD_TO_PROJECT_PAT }} notation-1.2.0/.github/workflows/build.yml000066400000000000000000000040711466375446100206060ustar00rootroot00000000000000# Copyright The Notary Project Authors. # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. name: build on: push: pull_request: permissions: contents: read jobs: build: if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name != github.repository name: Continuous Integration runs-on: ubuntu-latest strategy: matrix: go-version: ['1.23'] fail-fast: true steps: - name: Set up Go ${{ matrix.go-version }} uses: actions/setup-go@0a12ed9d6a96ab950c8f026ed9f722fe0da7ef32 # v5.0.2 with: go-version: ${{ matrix.go-version }} check-latest: true - name: Check out code uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - name: Cache Go modules uses: actions/cache@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2 id: go-mod-cache with: path: ~/go/pkg/mod key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} restore-keys: | ${{ runner.os }}-go- - name: Get dependencies run: make download - name: Build run: make build - name: Run unit tests run: make test - name: Run e2e tests run: | if [[ $GITHUB_REF_NAME == v* && $GITHUB_REF_TYPE == tag ]]; then make e2e else make e2e-covdata fi - name: Upload coverage to codecov.io uses: codecov/codecov-action@e28ff129e5465c2c0dcc6f003fc735cb6ae0c673 # v4.5.0 env: CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} notation-1.2.0/.github/workflows/codeql.yml000066400000000000000000000032031466375446100207520ustar00rootroot00000000000000# Copyright The Notary Project Authors. # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. name: "CodeQL" on: push: branches: - main - release-* pull_request: branches: - main - release-* schedule: - cron: '38 21 * * 1' permissions: contents: read jobs: analyze: name: Analyze runs-on: ubuntu-latest permissions: actions: read contents: read security-events: write strategy: matrix: go-version: ['1.23'] fail-fast: false steps: - name: Checkout repository uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - name: Set up Go ${{ matrix.go-version }} environment uses: actions/setup-go@0a12ed9d6a96ab950c8f026ed9f722fe0da7ef32 # v5.0.2 with: go-version: ${{ matrix.go-version }} check-latest: true - name: Initialize CodeQL uses: github/codeql-action/init@eb055d739abdc2e8de2e5f4ba1a8b246daa779aa # v3.26.0 with: languages: go - name: Perform CodeQL Analysis uses: github/codeql-action/analyze@eb055d739abdc2e8de2e5f4ba1a8b246daa779aa # v3.26.0 notation-1.2.0/.github/workflows/license-checker.yml000066400000000000000000000016511466375446100225340ustar00rootroot00000000000000# Copyright The Notary Project Authors. # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. name: License Checker on: push: branches: - main - release-* pull_request: branches: - main - release-* permissions: contents: read jobs: check-license: permissions: contents: write pull-requests: write uses: notaryproject/notation-core-go/.github/workflows/reusable-license-checker.yml@mainnotation-1.2.0/.github/workflows/release-github.yml000066400000000000000000000034601466375446100224100ustar00rootroot00000000000000# Copyright The Notary Project Authors. # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. name: release-github on: push: tags: - v* permissions: contents: read jobs: build: permissions: contents: write # for goreleaser/goreleaser-action to create a GitHub release name: Release Notation Binaries runs-on: ubuntu-20.04 strategy: matrix: go-version: ['1.23'] fail-fast: true steps: - name: Set up Go ${{ matrix.go-version }} uses: actions/setup-go@0a12ed9d6a96ab950c8f026ed9f722fe0da7ef32 # v5.0.2 with: go-version: ${{ matrix.go-version }} check-latest: true - name: Checkout uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 with: fetch-depth: 0 - name: Set GoReleaser Previous Tag To Be Last Non Weekly Release run: | pre_tag=`git tag --sort=-creatordate --list 'v*' | grep -v dev | head -2 | tail -1` echo "GORELEASER_PREVIOUS_TAG=$pre_tag" >> $GITHUB_ENV - name: Run GoReleaser uses: goreleaser/goreleaser-action@286f3b13b1b49da4ac219696163fb8c1c93e1200 # v6.0.0 with: distribution: goreleaser version: latest args: release --clean env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} notation-1.2.0/.github/workflows/scorecard.yml000066400000000000000000000036401466375446100214550ustar00rootroot00000000000000# Copyright The Notary Project Authors. # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. name: Scorecards supply-chain security on: branch_protection_rule: schedule: # Weekly on Saturdays. - cron: '30 1 * * 6' push: branches: - main - release-* paths: - '!docs/**' - '!specs/**' - '!*.md' workflow_dispatch: permissions: read-all jobs: analysis: name: Scorecards analysis runs-on: ubuntu-latest permissions: security-events: write id-token: write actions: read contents: read steps: - name: "Checkout code" uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # tag=4.1.7 with: persist-credentials: false - name: "Run analysis" uses: ossf/scorecard-action@62b2cac7ed8198b15735ed49ab1e5cf35480ba46 # tag=v2.4.0 with: results_file: results.sarif results_format: sarif repo_token: ${{ secrets.SCORECARD_READ_TOKEN }} publish_results: true - name: "Upload artifact" uses: actions/upload-artifact@834a144ee995460fba8ed112a2fc961b36a5ec5a # tag=v4.3.6 with: name: SARIF file path: results.sarif retention-days: 5 - name: "Upload to code-scanning" uses: github/codeql-action/upload-sarif@eb055d739abdc2e8de2e5f4ba1a8b246daa779aa # v3.26.0 with: sarif_file: results.sarif notation-1.2.0/.github/workflows/stale.yml000066400000000000000000000026711466375446100206230ustar00rootroot00000000000000# Copyright The Notary Project Authors. # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. name: "Close stale issues and PRs" on: schedule: - cron: "30 1 * * *" jobs: stale: runs-on: ubuntu-latest steps: - uses: actions/stale@v9 with: stale-issue-message: "This issue is stale because it has been opened for 60 days with no activity. Remove stale label or comment. Otherwise, it will be closed in 30 days." stale-pr-message: "This PR is stale because it has been opened for 45 days with no activity. Remove stale label or comment. Otherwise, it will be closed in 30 days." close-issue-message: "Issue closed due to no activity in the past 30 days." close-pr-message: "PR closed due to no activity in the past 30 days." days-before-issue-stale: 60 days-before-pr-stale: 45 days-before-issue-close: 30 days-before-pr-close: 30 exempt-all-milestones: true notation-1.2.0/.gitignore000066400000000000000000000012651466375446100153610ustar00rootroot00000000000000# Copyright The Notary Project Authors. # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # VS Code .vscode # Custom bin/ vendor/ coverage.txt test/e2e/coverage.txt **/covcounters.* **/covmeta.* dist/ notation-1.2.0/.goreleaser.yml000066400000000000000000000024361466375446100163230ustar00rootroot00000000000000# Copyright The Notary Project Authors. # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. builds: - main: ./cmd/notation id: notation binary: notation env: - CGO_ENABLED=0 goos: - linux - darwin - windows goarch: - amd64 - arm64 - arm goarm: - '7' ignore: - goos: windows goarch: arm64 - goos: windows goarch: arm - goos: darwin goarch: arm ldflags: - -s -w -X {{.ModulePath}}/internal/version.Version={{.Version}} -X {{.ModulePath}}/internal/version.GitCommit={{.FullCommit}} -X {{.ModulePath}}/internal/version.BuildMetadata= archives: - format: tar.gz format_overrides: - goos: windows format: zip files: - LICENSE release: draft: true prerelease: auto notation-1.2.0/CODEOWNERS000066400000000000000000000003151466375446100147570ustar00rootroot00000000000000# Repo-Level Owners (in alphabetical order) # Note: This is only for the notaryproject/notation repo * @gokarnm @JeyJeyGao @niazfk @priteshbandi @rgnote @shizhMSFT @toddysm @Two-Hearts @vaninrao10 @yizha1 notation-1.2.0/CODE_OF_CONDUCT.md000066400000000000000000000002021466375446100161560ustar00rootroot00000000000000# Code of Conduct Notation follows the [CNCF Code of Conduct](https://github.com/cncf/foundation/blob/master/code-of-conduct.md).notation-1.2.0/LICENSE000066400000000000000000000261351466375446100144010ustar00rootroot00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. notation-1.2.0/MAINTAINERS000066400000000000000000000015401466375446100150620ustar00rootroot00000000000000# Org-Level Maintainers (in alphabetical order) # Pattern: [First Name] [Last Name] <[Email Address]> ([GitHub Handle]) Niaz Khan (@niazfk) Pritesh Bandi (@priteshbandi) Shiwei Zhang (@shizhMSFT) Toddy Mladenov (@toddysm) Vani Rao (@vaninrao10) Yi Zha (@yizha1) # Repo-Level Maintainers (in alphabetical order) # Note: This is for the notaryproject/notation repo Junjie Gao (@JeyJeyGao) Milind Gokarn (@gokarnm) Patrick Zheng (@Two-Hearts) Rakesh Gariganti (@rgnote) # Emeritus Org Maintainers (in alphabetical order) Justin Cormack (@justincormack) Steve Lasker (@stevelasker)notation-1.2.0/Makefile000066400000000000000000000057041466375446100150330ustar00rootroot00000000000000# Copyright The Notary Project Authors. # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. MODULE = github.com/notaryproject/notation COMMANDS = notation GIT_TAG = $(shell git describe --tags --abbrev=0 --exact-match 2>/dev/null) GIT_COMMIT = $(shell git rev-parse HEAD) # if the commit was tagged, BuildMetadata is empty. ifndef BUILD_METADATA BUILD_METADATA := unreleased endif ifneq ($(GIT_TAG),) BUILD_METADATA := endif # set flags LDFLAGS := -w \ -X $(MODULE)/internal/version.GitCommit=$(GIT_COMMIT) \ -X $(MODULE)/internal/version.BuildMetadata=$(BUILD_METADATA) GO_BUILD_FLAGS = --ldflags="$(LDFLAGS)" .PHONY: help help: @grep -E '^[a-zA-Z0-9_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-25s\033[0m %s\n", $$1, $$2}' .PHONY: all all: build .PHONY: FORCE FORCE: bin/%: cmd/% FORCE go build $(GO_INSTRUMENT_FLAGS) $(GO_BUILD_FLAGS) -o $@ ./$< .PHONY: download download: ## download dependencies via go mod go mod download .PHONY: build build: $(addprefix bin/,$(COMMANDS)) ## builds binaries .PHONY: test test: vendor check-line-endings ## run unit tests go test -race -v -coverprofile=coverage.txt -covermode=atomic ./... .PHONY: e2e e2e: build ## build notation cli and run e2e test NOTATION_BIN_PATH=`pwd`/bin/$(COMMANDS); \ cd ./test/e2e; \ ./run.sh zot $$NOTATION_BIN_PATH; \ .PHONY: e2e-covdata e2e-covdata: export GOCOVERDIR=$(CURDIR)/test/e2e/.cover; \ rm -rf $$GOCOVERDIR; \ mkdir -p $$GOCOVERDIR; \ export GO_INSTRUMENT_FLAGS='-coverpkg "github.com/notaryproject/notation/internal/...,github.com/notaryproject/notation/pkg/...,github.com/notaryproject/notation/cmd/..."'; \ $(MAKE) e2e && go tool covdata textfmt -i=$$GOCOVERDIR -o "$(CURDIR)/test/e2e/coverage.txt" .PHONY: clean clean: git status --ignored --short | grep '^!! ' | sed 's/!! //' | xargs rm -rf .PHONY: check-line-endings check-line-endings: ## check line endings ! find cmd pkg internal -name "*.go" -type f -exec file "{}" ";" | grep CRLF .PHONY: fix-line-endings fix-line-endings: ## fix line endings find cmd pkg internal -type f -name "*.go" -exec sed -i -e "s/\r//g" {} + .PHONY: vendor vendor: ## vendores the go modules GO111MODULE=on go mod vendor .PHONY: install install: install-notation ## install the notation cli .PHONY: install-notation install-notation: bin/notation ## installs the notation cli mkdir -p ~/bin cp $< ~/bin/ .PHONY: install-docker-% install-docker-%: bin/docker-% cp $< ~/.docker/cli-plugins/ notation-1.2.0/README.md000066400000000000000000000077421466375446100146560ustar00rootroot00000000000000# Notation [![Go Report Card](https://goreportcard.com/badge/github.com/notaryproject/notation)](https://goreportcard.com/report/github.com/notaryproject/notation) [![codecov](https://codecov.io/gh/notaryproject/notation/branch/main/graph/badge.svg)](https://codecov.io/gh/notaryproject/notation) [![OpenSSF Scorecard](https://api.securityscorecards.dev/projects/github.com/notaryproject/notation/badge)](https://api.securityscorecards.dev/projects/github.com/notaryproject/notation) Notation is a CLI project to add signatures as standard items in the OCI registry ecosystem, and to build a set of simple tooling for signing and verifying these signatures. This should be viewed as similar security to checking git commit signatures, although the signatures are generic and can be used for additional purposes. Notation is an implementation of the [Notary Project specifications][notaryproject-specs]. You can find the Notary Project [README](https://github.com/notaryproject/.github/blob/main/README.md) to learn about the overall Notary Project. > [!NOTE] > The documentation for installing Notation CLI is available [here](https://notaryproject.dev/docs/user-guides/installation/cli/). ## Table of Contents - [Quick Start](#quick-start) - [Community](#community) - [Development and Contributing](#development-and-contributing) - [Notary Project Community Meeting](#notary-project-community-meeting) - [Release Management](#release-management) - [Support](#support) - [Code of Conduct](#code-of-conduct) - [License](#license) ## Quick Start - [Quick start: Sign and validate a container image](https://notaryproject.dev/docs/quickstart-guides/quickstart-sign-image-artifact/) - [Try out Notation in this Killercoda interactive sandbox environment](https://killercoda.com/notaryproject/scenario/notation) - Build, sign, and verify container images using Notation with [Azure Key Vault](https://docs.microsoft.com/azure/container-registry/container-registry-tutorial-sign-build-push?wt.mc_id=azurelearn_inproduct_oss_notaryproject) or [AWS Signer](https://docs.aws.amazon.com/signer/latest/developerguide/container-workflow.html) ## Community Notary Project is a [CNCF Incubating project](https://www.cncf.io/projects/notary/). We :heart: your contribution. ### Development and Contributing - [Build Notation from source code](/building.md) - [Governance for Notary Project](https://github.com/notaryproject/.github/blob/master/GOVERNANCE.md) - [Maintainers and reviewers list](https://github.com/notaryproject/notation/blob/main/CODEOWNERS) - Regular conversations for Notary Project occur on the [Cloud Native Computing Slack](https://slack.cncf.io/) **notary-project** channel. ### Notary Project Community Meeting - Mondays 5-6 PM PDT, 4-5 PM PST, 8-9 PM EDT, 7-8 PM EST, 8-9 AM Shanghai - Thursdays 9-10 AM PDT, 8-9 AM PST, 12 PM EDT, 11 AM EST, 5 PM UK Join us at [Zoom Dial-in link](https://zoom.us/my/cncfnotaryproject) / Passcode: 77777. Please see the [CNCF Calendar](https://www.cncf.io/calendar/) for community meeting details. Meeting notes are captured on [hackmd.io](https://hackmd.io/_vrqBGAOSUC_VWvFzWruZw). ## Release Management The Notation release process is defined in [RELEASE_MANAGEMENT.md](RELEASE_MANAGEMENT.md#supported-releases). ## Support Support for the Notation project is defined in [supported releases](RELEASE_MANAGEMENT.md#supported-releases). ## Code of Conduct This project has adopted the [CNCF Code of Conduct](https://github.com/cncf/foundation/blob/master/code-of-conduct.md). See [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md) for further details. ## License This project is covered under the Apache 2.0 license. You can read the license [here](LICENSE). [notation-releases]: https://github.com/notaryproject/notation/releases [notaryproject-specs]: https://github.com/notaryproject/notaryproject [artifact-manifest]: https://github.com/oras-project/artifacts-spec/blob/main/artifact-manifest.md [cncf-distribution]: https://github.com/oras-project/distribution notation-1.2.0/RELEASE_CHECKLIST.md000066400000000000000000000072151466375446100163650ustar00rootroot00000000000000# Release Checklist ## Overview This document describes the checklist to publish a release for Notation CLI via GitHub workflow. ## Release Process - Check if there are any security vulnerabilities fixed and security advisories published before a release. Security advisories should be linked on the release notes. - Determine a [SemVer2](https://semver.org/)-valid version prefixed with the letter `v` for release. For example, `version="v1.0.0-alpha.1"`. - If there is new release in [notation-go](https://github.com/notaryproject/notation-go) or [notation-core-go](https://github.com/notaryproject/notation-core-go) library that are required to be upgraded in Notation CLI, update the dependency versions in the follow `go.mod` and `go.sum` files of Notation CLI: - [go.mod](go.mod), [go.sum](go.sum) - [test/e2e/go.mod](test/e2e/go.mod), [test/e2e/go.sum](test/e2e/go.sum) - [test/e2e/plugin/go.mod](test/e2e/plugin/go.mod) and [test/e2e/plugin/go.sum](test/e2e/plugin/go.sum) - Open a PR submit the changes in the previous step to the notation repository. Please make sure this PR is merged with all E2E test cases passed before starting the next step. See [PR #754](https://github.com/notaryproject/notation/pull/754) as an example. - Create another PR to update the Notation CLI version with a single commit when PRs in above steps are merged. The commit message MUST follow the [conventional commit](https://www.conventionalcommits.org/en/v1.0.0/) and could be `bump: tag and release $version`. Record the digest of that commit as ``. This PR is also used for voting purpose of the new release. Add the link of change logs and repo-level maintainer list in the PR's description. The PR title could be `bump: tag and release $version`. Make sure to reach a majority of approvals from the [repo-level maintainers](MAINTAINERS) before releasing it. This PR should be merged using [Create a merge commit](https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/configuring-pull-request-merges/about-merge-methods-on-github) method in GitHub. See [PR #748](https://github.com/notaryproject/notation/pull/748) as an example. - After the voting PR is merged in the [Notation](https://github.com/notaryproject/notation.git) repository, execute `git clone git@github.com:notaryproject/notation.git` to clone the repository to your local file system. - Enter the cloned repository and execute `git checkout ` to switch to the specified branch based on the voting result. - Create a tag by running `git tag -am $version $version -s`. - Run `git tag` and ensure the desired tag name in the list looks correct, then push the new tag directly to the repository by running `git push origin $version`. - Wait for the completion of the GitHub action [release-github](https://github.com/notaryproject/notation/actions/workflows/release-github.yml). - Check the new draft release, revise the release description, and publish the release. - Update the necessary documentation in the [notaryproject.dev](https://github.com/notaryproject/notaryproject.dev) repository to reflect the changes of the release on the Notary Project website, includes but not limited to [installation guide](https://github.com/notaryproject/notaryproject.dev/blob/main/content/en/docs/installation/cli.md), [user guide](https://github.com/notaryproject/notaryproject.dev/tree/main/content/en/docs/how-to), [banner](https://github.com/notaryproject/notaryproject.dev/blob/main/layouts/partials/banner.html), [release blog](https://github.com/notaryproject/notaryproject.dev/tree/main/content/en/blog). - Announce the new release in the Notary Project community. notation-1.2.0/RELEASE_MANAGEMENT.md000066400000000000000000000072601466375446100164700ustar00rootroot00000000000000# Release Management ## Overview This document describes Notation project release management, which includes release versioning, supported releases, and supported upgrades. ## Glossary of Terms - **X.Y.Z** refers to the version (based on git tag) of Notation that is released. This is the version of the Notation binary. - **Breaking changes** refer to schema changes, flag changes, and behavior changes of Notation that may require existing content to be upgraded and may also introduce changes that could break backward compatibility. - **Milestone** GitHub milestones are used by maintainers to manage each release. PRs and Issues for each release should be created as part of a corresponding milestone. - **Patch releases** refer to applicable fixes, including security fixes, may be backported to support releases, depending on severity and feasibility. ## Release Versioning All releases will be of the form _vX.Y.Z_ where X is the major version, Y is the minor version and Z is the patch version. This project strictly follows semantic versioning. The rest of the doc will cover the release process for the following kinds of releases: ### Major Releases The Notation project has reached a stable target version of 1.0.0 and all previous RC, BETA, and ALPHA releases will no longer be supported. It's recommended to upgrade the Notation version to v1.0.0 if you are still using a previous version. ### Minor Releases - **ALPHA:** X.Y.0-alpha.W, W >= 0 (Branch : main) - Alpha release, cut from main branch - Unstable release which should only be used for early development purposes - Released as needed before we cut a beta X.Y release - Not supported - **BETA:** X.Y.0-beta.W, W >= 0 (Branch : main) - More stable than the alpha release to be used for testing purposes only - Beta release, cut from main branch - Released as needed before we cut a stable X.Y release - Not supported - **RC:** X.Y.0-rc.W, W >= 0 (Branch : main) - Released as needed before we cut a stable X.Y release - soak for ~ 2 weeks before cutting a stable release - Bugfixes on new features only as reported through usage - Release candidate release, cut from main branch - Not supported - **STABLE:** X.Y.0 (Branch: main) - Stable release, cut from master when X.Y milestone is complete - X.Y release branch cut for subsequent patch releases - Supported as per the supported releases process defined below ### Patch Releases - Patch Releases X.Y.Z, Z > 0 (Branch: release-X.Y, only cut when a patch is needed) - No breaking changes - Applicable fixes, including security fixes, may be cherry-picked from master into the latest supported minor release-X.Y branches. - Patch release, cut from a release-X.Y branch ## Supported Releases We expect to "support" n (current) and n-1 major.minor releases. "Support" means we expect users to be running that version in production. For example, when v1.3.0 comes out, v1.1.x will no longer be supported for patches, and we encourage users to upgrade to a supported version as soon as possible. Support will be provided best effort by the maintainers via GitHub issues and pull requests. We expect users to stay up-to-date with the versions of Notation they use in production, but understand that it may take time to upgrade. We expect users to be running approximately the latest patch release of a given minor release and encourage users to upgrade as soon as possible. Applicable fixes, including security fixes, may be cherry-picked into the release branch, depending on severity and feasibility. Patch releases are cut from that branch as needed. ## Attribution This document builds on the ideas and implementations of release processes from Kubernetes, Helm, and Gatekeeper. notation-1.2.0/building.md000066400000000000000000000016601466375446100155070ustar00rootroot00000000000000# Building Notation The notation repo contains the following: - `notation` - A CLI for signing and verifying artifacts with Notation Building above binaries require [golang](https://golang.org/dl/) with version `>= 1.23`. ## Windows with WSL or Linux - Build the binaries, installing them to: - `~/bin/notation` ```sh git clone https://github.com/notaryproject/notation.git cd notation make install ``` - Verify binaries are installed ```sh which notation # expected output /home//bin/notation ``` If you confront `notation not found`, please add `~/bin/` to your $PATH: ```sh PATH="$HOME/bin:$PATH" ``` If you would like to add the path permanently, add the command to your shell `profile`: ```sh echo 'PATH="$HOME/bin:$PATH"' >> $profile_path source $profile_path ``` The `profile_path` per shell: - Bash: `~/.bash_profile` or `~/.profile` - Zsh: `~/.zprofile` - Ksh: `~/.profile` notation-1.2.0/cmd/000077500000000000000000000000001466375446100141305ustar00rootroot00000000000000notation-1.2.0/cmd/notation/000077500000000000000000000000001466375446100157635ustar00rootroot00000000000000notation-1.2.0/cmd/notation/cert/000077500000000000000000000000001466375446100167205ustar00rootroot00000000000000notation-1.2.0/cmd/notation/cert/add.go000066400000000000000000000064761466375446100200140ustar00rootroot00000000000000// Copyright The Notary Project Authors. // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package cert import ( "errors" "fmt" "github.com/notaryproject/notation/cmd/notation/internal/truststore" "github.com/spf13/cobra" ) type certAddOpts struct { storeType string namedStore string path []string } func certAddCommand(opts *certAddOpts) *cobra.Command { if opts == nil { opts = &certAddOpts{} } command := &cobra.Command{ Use: "add --type --store [flags] ...", Short: "Add certificates to the trust store.", Args: func(cmd *cobra.Command, args []string) error { if len(args) == 0 { return errors.New("missing certificate path") } opts.path = args return nil }, Long: `Add certificates to the trust store Example - Add a certificate to the "ca" type of a named store "acme-rockets": notation cert add --type ca --store acme-rockets acme-rockets.crt Example - Add a certificate to the "signingAuthority" type of a named store "wabbit-networks": notation cert add --type signingAuthority --store wabbit-networks wabbit-networks.pem Example - Add a certificate to the "tsa" type of a named store "timestamp": notation cert add --type tsa --store timestamp wabbit-networks-timestamp.pem `, RunE: func(cmd *cobra.Command, args []string) error { return addCerts(opts) }, } command.Flags().StringVarP(&opts.storeType, "type", "t", "", "specify trust store type, options: ca, signingAuthority") command.Flags().StringVarP(&opts.namedStore, "store", "s", "", "specify named store") command.MarkFlagRequired("type") command.MarkFlagRequired("store") return command } func addCerts(opts *certAddOpts) error { storeType := opts.storeType if storeType == "" { return errors.New("store type cannot be empty") } if !truststore.IsValidStoreType(storeType) { return fmt.Errorf("unsupported store type: %s", storeType) } namedStore := opts.namedStore if !truststore.IsValidFileName(namedStore) { return errors.New("named store name needs to follow [a-zA-Z0-9_.-]+ format") } var success []string var failure []string var errorSlice []error for _, p := range opts.path { err := truststore.AddCert(p, storeType, namedStore, false) if err != nil { failure = append(failure, p) errorSlice = append(errorSlice, err) } else { success = append(success, p) } } //write out if len(success) != 0 { fmt.Printf("Successfully added following certificates to named store %s of type %s:\n", namedStore, storeType) for _, p := range success { fmt.Println(p) } } if len(failure) != 0 { errStr := fmt.Sprintf("Failed to add following certificates to named store %s of type %s:\n", namedStore, storeType) for ind := range failure { errStr = errStr + fmt.Sprintf("%s, with error %q\n", failure[ind], errorSlice[ind]) } return errors.New(errStr) } return nil } notation-1.2.0/cmd/notation/cert/add_test.go000066400000000000000000000027041466375446100210410ustar00rootroot00000000000000// Copyright The Notary Project Authors. // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package cert import ( "reflect" "testing" ) func TestCertAddCommand(t *testing.T) { opts := &certAddOpts{} cmd := certAddCommand(opts) expected := &certAddOpts{ storeType: "ca", namedStore: "test", path: []string{"path"}, } if err := cmd.ParseFlags([]string{ "path", "-t", "ca", "-s", "test"}); err != nil { t.Fatalf("Parse Flag failed: %v", err) } if err := cmd.Args(cmd, cmd.Flags().Args()); err != nil { t.Fatalf("Parse Args failed: %v", err) } if !reflect.DeepEqual(*expected, *opts) { t.Fatalf("Expect cert add opts: %v, got: %v", expected, opts) } } func TestCertAddCommand_MissingArgs(t *testing.T) { cmd := certAddCommand(nil) if err := cmd.ParseFlags([]string{}); err != nil { t.Fatalf("Parse Flag failed: %v", err) } if err := cmd.Args(cmd, cmd.Flags().Args()); err == nil { t.Fatal("Parse Args expected error, but ok") } } notation-1.2.0/cmd/notation/cert/cmd.go000066400000000000000000000020351466375446100200120ustar00rootroot00000000000000// Copyright The Notary Project Authors. // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package cert import "github.com/spf13/cobra" func Cmd() *cobra.Command { command := &cobra.Command{ Use: "certificate", Aliases: []string{"cert"}, Short: "Manage certificates in trust store", Long: "Manage certificates in trust store for signature verification.", } command.AddCommand( certAddCommand(nil), certListCommand(nil), certShowCommand(nil), certDeleteCommand(nil), certGenerateTestCommand(nil), ) return command } notation-1.2.0/cmd/notation/cert/delete.go000066400000000000000000000076641466375446100205260ustar00rootroot00000000000000// Copyright The Notary Project Authors. // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package cert import ( "errors" "fmt" "github.com/notaryproject/notation/cmd/notation/internal/truststore" "github.com/spf13/cobra" ) type certDeleteOpts struct { storeType string namedStore string cert string all bool confirmed bool } func certDeleteCommand(opts *certDeleteOpts) *cobra.Command { if opts == nil { opts = &certDeleteOpts{} } command := &cobra.Command{ Use: "delete --type --store [flags] (--all | )", Short: "Delete certificates from the trust store.", Args: func(cmd *cobra.Command, args []string) error { if opts.all { if len(args) != 0 { return errors.New("cannot delete a single certificate file when --all flag is set. use --help flag for more details") } return nil } if len(args) == 0 { return errors.New("delete requires either the certificate file name that needs to be deleted or --all flag to delete all certificates in the given named trust store") } opts.cert = args[0] return nil }, Long: `Delete certificates from the trust store Example - Delete all certificates with "ca" type from the trust store "acme-rockets": notation cert delete --type ca --store acme-rockets --all Example - Delete certificate "cert1.pem" with "signingAuthority" type from trust store wabbit-networks: notation cert delete --type signingAuthority --store wabbit-networks cert1.pem Example - Delete all certificates with "ca" type from the trust store "acme-rockets", without prompt for confirmation: notation cert delete --type ca --store acme-rockets -y --all Example - Delete certificate "wabbit-networks-timestamp.pem" with "tsa" type from trust store timestamp: notation cert delete --type tsa --store timestamp wabbit-networks-timestamp.pem -y `, RunE: func(cmd *cobra.Command, args []string) error { return deleteCerts(opts) }, } command.Flags().StringVarP(&opts.storeType, "type", "t", "", "specify trust store type, options: ca, signingAuthority") command.Flags().StringVarP(&opts.namedStore, "store", "s", "", "specify named store") command.Flags().BoolVarP(&opts.all, "all", "a", false, "delete all certificates in the named store") command.Flags().BoolVarP(&opts.confirmed, "yes", "y", false, "do not prompt for confirmation") command.MarkFlagRequired("type") command.MarkFlagRequired("store") return command } func deleteCerts(opts *certDeleteOpts) error { namedStore := opts.namedStore if !truststore.IsValidFileName(namedStore) { return errors.New("named store name needs to follow [a-zA-Z0-9_.-]+ format") } storeType := opts.storeType if storeType == "" { return errors.New("store type cannot be empty") } if !truststore.IsValidStoreType(storeType) { return fmt.Errorf("unsupported store type: %s", storeType) } if opts.all { // Delete all certificates under storeType/namedStore err := truststore.DeleteAllCerts(storeType, namedStore, opts.confirmed) if err != nil { return fmt.Errorf("failed to delete the named trust store: %w", err) } return nil } // Delete a certain certificate with path storeType/namedStore/cert cert := opts.cert if cert == "" { return errors.New("to delete a specific certificate, certificate fileName cannot be empty") } err := truststore.DeleteCert(storeType, namedStore, cert, opts.confirmed) if err != nil { return fmt.Errorf("failed to delete the certificate file: %w", err) } return nil } notation-1.2.0/cmd/notation/cert/delete_test.go000066400000000000000000000027631466375446100215600ustar00rootroot00000000000000// Copyright The Notary Project Authors. // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package cert import ( "reflect" "testing" ) func TestCertDeleteCommand(t *testing.T) { opts := &certDeleteOpts{} cmd := certDeleteCommand(opts) expected := &certDeleteOpts{ storeType: "ca", namedStore: "test", cert: "test.crt", confirmed: true, } if err := cmd.ParseFlags([]string{ "test.crt", "-t", "ca", "-s", "test", "-y"}); err != nil { t.Fatalf("Parse Flag failed: %v", err) } if err := cmd.Args(cmd, cmd.Flags().Args()); err != nil { t.Fatalf("Parse Args failed: %v", err) } if !reflect.DeepEqual(*expected, *opts) { t.Fatalf("Expect cert delete opts: %v, got: %v", expected, opts) } } func TestCertDeleteCommand_MissingArgs(t *testing.T) { cmd := certDeleteCommand(nil) if err := cmd.ParseFlags([]string{}); err != nil { t.Fatalf("Parse Flag failed: %v", err) } if err := cmd.Args(cmd, cmd.Flags().Args()); err == nil { t.Fatal("Parse Args expected error, but ok") } } notation-1.2.0/cmd/notation/cert/generateTest.go000066400000000000000000000117771466375446100217160ustar00rootroot00000000000000// Copyright The Notary Project Authors. // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package cert import ( "crypto/rand" "crypto/rsa" "crypto/x509" "encoding/pem" "errors" "fmt" "time" "github.com/notaryproject/notation-core-go/testhelper" "github.com/notaryproject/notation-go/config" "github.com/notaryproject/notation-go/dir" "github.com/notaryproject/notation/cmd/notation/internal/truststore" "github.com/notaryproject/notation/internal/osutil" "github.com/spf13/cobra" "github.com/spf13/pflag" ) var ( keyDefaultFlag = &pflag.Flag{ Name: "default", Usage: "mark as default signing key", } setKeyDefaultFlag = func(fs *pflag.FlagSet, p *bool) { fs.BoolVarP(p, keyDefaultFlag.Name, keyDefaultFlag.Shorthand, false, keyDefaultFlag.Usage) } ) type certGenerateTestOpts struct { name string bits int isDefault bool } func certGenerateTestCommand(opts *certGenerateTestOpts) *cobra.Command { if opts == nil { opts = &certGenerateTestOpts{} } command := &cobra.Command{ Use: "generate-test [flags] ", Short: "Generate a test RSA key and a corresponding self-signed certificate.", Args: func(cmd *cobra.Command, args []string) error { if len(args) == 0 { return errors.New("missing certificate common_name") } opts.name = args[0] return nil }, Long: `Generate a test RSA key and a corresponding self-signed certificate Example - Generate a test RSA key and a corresponding self-signed certificate named "wabbit-networks.io": notation cert generate-test "wabbit-networks.io" Example - Generate a test RSA key and a corresponding self-signed certificate, set RSA key as a default signing key: notation cert generate-test --default "wabbit-networks.io" `, RunE: func(cmd *cobra.Command, args []string) error { return generateTestCert(opts) }, } command.Flags().IntVarP(&opts.bits, "bits", "b", 2048, "RSA key bits") setKeyDefaultFlag(command.Flags(), &opts.isDefault) return command } func generateTestCert(opts *certGenerateTestOpts) error { // initialize name := opts.name if !truststore.IsValidFileName(name) { return errors.New("name needs to follow [a-zA-Z0-9_.-]+ format") } // generate RSA private key bits := opts.bits fmt.Println("generating RSA Key with", bits, "bits") key, keyBytes, err := generateTestKey(bits) if err != nil { return err } rsaCertTuple, certBytes, err := generateSelfSignedCert(key, name) if err != nil { return err } fmt.Println("generated certificate expiring on", rsaCertTuple.Cert.NotAfter.Format(time.RFC3339)) // write private key relativeKeyPath, relativeCertPath := dir.LocalKeyPath(name) configFS := dir.ConfigFS() keyPath, err := configFS.SysPath(relativeKeyPath) if err != nil { return err } certPath, err := configFS.SysPath(relativeCertPath) if err != nil { return err } if err := osutil.WriteFileWithPermission(keyPath, keyBytes, 0600, false); err != nil { return fmt.Errorf("failed to write key file: %v", err) } fmt.Println("wrote key:", keyPath) // write the self-signed certificate if err := osutil.WriteFileWithPermission(certPath, certBytes, 0644, false); err != nil { return fmt.Errorf("failed to write certificate file: %v", err) } fmt.Println("wrote certificate:", certPath) // update signingkeys.json config exec := func(s *config.SigningKeys) error { return s.Add(opts.name, keyPath, certPath, opts.isDefault) } if err := config.LoadExecSaveSigningKeys(exec); err != nil { return err } // Add to the trust store if err := truststore.AddCert(certPath, "ca", name, true); err != nil { return err } // write out fmt.Printf("%s: added to the key list\n", name) if opts.isDefault { fmt.Printf("%s: mark as default signing key\n", name) } return nil } func generateTestKey(bits int) (*rsa.PrivateKey, []byte, error) { key, err := rsa.GenerateKey(rand.Reader, bits) if err != nil { return nil, nil, err } keyBytes, err := x509.MarshalPKCS8PrivateKey(key) if err != nil { return nil, nil, err } keyPEM := pem.EncodeToMemory(&pem.Block{Type: "PRIVATE KEY", Bytes: keyBytes}) return key, keyPEM, nil } func generateCertPEM(rsaCertTuple *testhelper.RSACertTuple) []byte { return pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: rsaCertTuple.Cert.Raw}) } // generateTestCert generates a self-signed non-CA certificate func generateSelfSignedCert(privateKey *rsa.PrivateKey, name string) (testhelper.RSACertTuple, []byte, error) { rsaCertTuple := testhelper.GetRSASelfSignedCertTupleWithPK(privateKey, name) return rsaCertTuple, generateCertPEM(&rsaCertTuple), nil } notation-1.2.0/cmd/notation/cert/generate_test.go000066400000000000000000000030111466375446100220730ustar00rootroot00000000000000// Copyright The Notary Project Authors. // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package cert import ( "fmt" "reflect" "testing" ) func TestCertGenerateCommand(t *testing.T) { opts := &certGenerateTestOpts{} cmd := certGenerateTestCommand(opts) expected := &certGenerateTestOpts{ name: "name", bits: 2048, isDefault: true, } if err := cmd.ParseFlags([]string{ "name", "--bits", fmt.Sprint(expected.bits), "--default"}); err != nil { t.Fatalf("Parse Flag failed: %v", err) } if err := cmd.Args(cmd, cmd.Flags().Args()); err != nil { t.Fatalf("Parse Args failed: %v", err) } if !reflect.DeepEqual(*expected, *opts) { t.Fatalf("Expect cert generate-test opts: %v, got: %v", expected, opts) } } func TestCertGenerateTestCommand_MissingArgs(t *testing.T) { cmd := certGenerateTestCommand(nil) if err := cmd.ParseFlags(nil); err != nil { t.Fatalf("Parse Flag failed: %v", err) } if err := cmd.Args(cmd, cmd.Flags().Args()); err == nil { t.Fatal("Parse Args expected error, but ok") } } notation-1.2.0/cmd/notation/cert/list.go000066400000000000000000000127341466375446100202310ustar00rootroot00000000000000// Copyright The Notary Project Authors. // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package cert import ( "context" "fmt" "os" "github.com/notaryproject/notation-go/dir" "github.com/notaryproject/notation-go/log" notationgoTruststore "github.com/notaryproject/notation-go/verifier/truststore" "github.com/notaryproject/notation/cmd/notation/internal/truststore" "github.com/notaryproject/notation/internal/cmd" "github.com/notaryproject/notation/internal/ioutil" "github.com/spf13/cobra" ) type certListOpts struct { cmd.LoggingFlagOpts storeType string namedStore string } func certListCommand(opts *certListOpts) *cobra.Command { if opts == nil { opts = &certListOpts{} } command := &cobra.Command{ Use: "list", Aliases: []string{"ls"}, Short: "List certificates in the trust store.", Long: `List certificates in the trust store Example - List all certificate files stored in the trust store notation cert ls Example - List all certificate files of trust store "acme-rockets" notation cert ls --store "acme-rockets" Example - List all certificate files from trust store of type "ca" notation cert ls --type ca Example - List all certificate files from trust store "wabbit-networks" of type "signingAuthority" notation cert ls --type signingAuthority --store "wabbit-networks" Example - List all certificate files from trust store of type "tsa" notation cert ls --type tsa `, RunE: func(cmd *cobra.Command, args []string) error { return listCerts(cmd.Context(), opts) }, } opts.LoggingFlagOpts.ApplyFlags(command.Flags()) command.Flags().StringVarP(&opts.storeType, "type", "t", "", "specify trust store type, options: ca, signingAuthority") command.Flags().StringVarP(&opts.namedStore, "store", "s", "", "specify named store") return command } func listCerts(ctx context.Context, opts *certListOpts) error { // set log level ctx = opts.LoggingFlagOpts.InitializeLogger(ctx) logger := log.GetLogger(ctx) namedStore := opts.namedStore storeType := opts.storeType configFS := dir.ConfigFS() // List all certificates under truststore/x509, display empty if there's // no certificate yet if namedStore == "" && storeType == "" { var certPaths []string for _, t := range notationgoTruststore.Types { path, err := configFS.SysPath(dir.TrustStoreDir, "x509", string(t)) if err := truststore.CheckNonErrNotExistError(err); err != nil { return err } certs, err := truststore.ListCerts(path, 1) if err := truststore.CheckNonErrNotExistError(err); err != nil { logger.Debugln("Failed to complete list at path:", path) return fmt.Errorf("failed to list all certificates stored in the trust store, with error: %s", err.Error()) } certPaths = append(certPaths, certs...) } return ioutil.PrintCertMap(os.Stdout, certPaths) } // List all certificates under truststore/x509/storeType/namedStore, // display empty if store type is invalid or there's no certificate yet if namedStore != "" && storeType != "" { if !truststore.IsValidStoreType(storeType) { return nil } path, err := configFS.SysPath(dir.TrustStoreDir, "x509", storeType, namedStore) if err := truststore.CheckNonErrNotExistError(err); err != nil { return err } certPaths, err := truststore.ListCerts(path, 0) if err := truststore.CheckNonErrNotExistError(err); err != nil { logger.Debugln("Failed to complete list at path:", path) return fmt.Errorf("failed to list all certificates stored in the named store %s of type %s, with error: %s", namedStore, storeType, err.Error()) } return ioutil.PrintCertMap(os.Stdout, certPaths) } // List all certificates under x509/storeType, display empty if store type // is invalid or there's no certificate yet if storeType != "" { if !truststore.IsValidStoreType(storeType) { return nil } path, err := configFS.SysPath(dir.TrustStoreDir, "x509", storeType) if err := truststore.CheckNonErrNotExistError(err); err != nil { return err } certPaths, err := truststore.ListCerts(path, 1) if err := truststore.CheckNonErrNotExistError(err); err != nil { logger.Debugln("Failed to complete list at path:", path) return fmt.Errorf("failed to list all certificates stored of type %s, with error: %s", storeType, err.Error()) } return ioutil.PrintCertMap(os.Stdout, certPaths) } // List all certificates under named store namedStore, display empty if // there's no certificate yet var certPaths []string for _, t := range notationgoTruststore.Types { path, err := configFS.SysPath(dir.TrustStoreDir, "x509", string(t), namedStore) if err := truststore.CheckNonErrNotExistError(err); err != nil { return err } certs, err := truststore.ListCerts(path, 0) if err := truststore.CheckNonErrNotExistError(err); err != nil { logger.Debugln("Failed to complete list at path:", path) return fmt.Errorf("failed to list all certificates stored in the named store %s, with error: %s", namedStore, err.Error()) } certPaths = append(certPaths, certs...) } return ioutil.PrintCertMap(os.Stdout, certPaths) } notation-1.2.0/cmd/notation/cert/list_test.go000066400000000000000000000020301466375446100212540ustar00rootroot00000000000000// Copyright The Notary Project Authors. // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package cert import ( "reflect" "testing" ) func TestCertListCommand(t *testing.T) { opts := &certListOpts{} cmd := certListCommand(opts) expected := &certListOpts{ storeType: "ca", namedStore: "test", } if err := cmd.ParseFlags([]string{ "-t", "ca", "-s", "test"}); err != nil { t.Fatalf("Parse Flag failed: %v", err) } if !reflect.DeepEqual(*expected, *opts) { t.Fatalf("Expect cert list opts: %v, got: %v", expected, opts) } } notation-1.2.0/cmd/notation/cert/show.go000066400000000000000000000101551466375446100202310ustar00rootroot00000000000000// Copyright The Notary Project Authors. // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package cert import ( "context" "errors" "fmt" corex509 "github.com/notaryproject/notation-core-go/x509" "github.com/notaryproject/notation-go/dir" "github.com/notaryproject/notation-go/log" "github.com/notaryproject/notation/cmd/notation/internal/truststore" "github.com/notaryproject/notation/internal/cmd" "github.com/spf13/cobra" ) type certShowOpts struct { cmd.LoggingFlagOpts storeType string namedStore string cert string } func certShowCommand(opts *certShowOpts) *cobra.Command { if opts == nil { opts = &certShowOpts{} } command := &cobra.Command{ Use: "show --type --store [flags] ", Short: "Show certificate details given trust store type, named store, and certificate file name. If the certificate file contains multiple certificates, then all certificates are displayed.", Args: func(cmd *cobra.Command, args []string) error { if len(args) == 0 { return errors.New("missing certificate file name") } if len(args) > 1 { return errors.New("show only supports single certificate file") } opts.cert = args[0] return nil }, Long: `Show certificate details of given trust store name, trust store type, and certificate file name. If the certificate file contains multiple certificates, then all certificates are displayed Example - Show details of certificate "cert1.pem" with type "ca" from trust store "acme-rockets": notation cert show --type ca --store acme-rockets cert1.pem Example - Show details of certificate "cert2.pem" with type "signingAuthority" from trust store "wabbit-networks": notation cert show --type signingAuthority --store wabbit-networks cert2.pem Example - Show details of certificate "wabbit-networks-timestamp.pem" with type "tsa" from trust store "timestamp": notation cert show --type tsa --store timestamp wabbit-networks-timestamp.pem `, RunE: func(cmd *cobra.Command, args []string) error { return showCerts(cmd.Context(), opts) }, } opts.LoggingFlagOpts.ApplyFlags(command.Flags()) command.Flags().StringVarP(&opts.storeType, "type", "t", "", "specify trust store type, options: ca, signingAuthority") command.Flags().StringVarP(&opts.namedStore, "store", "s", "", "specify named store") command.MarkFlagRequired("type") command.MarkFlagRequired("store") return command } func showCerts(ctx context.Context, opts *certShowOpts) error { // set log level ctx = opts.LoggingFlagOpts.InitializeLogger(ctx) logger := log.GetLogger(ctx) storeType := opts.storeType if storeType == "" { return errors.New("store type cannot be empty") } if !truststore.IsValidStoreType(storeType) { return fmt.Errorf("unsupported store type: %s", storeType) } namedStore := opts.namedStore if !truststore.IsValidFileName(namedStore) { return errors.New("named store name needs to follow [a-zA-Z0-9_.-]+ format") } cert := opts.cert if cert == "" { return errors.New("certificate fileName cannot be empty") } path, err := dir.ConfigFS().SysPath(dir.TrustStoreDir, "x509", storeType, namedStore, cert) if err != nil { return fmt.Errorf("failed to show details of certificate %s, with error: %s", cert, err.Error()) } logger.Debugln("Showing details of certificate:", path) certs, err := corex509.ReadCertificateFile(path) if err != nil { return fmt.Errorf("failed to show details of certificate %s, with error: %s", cert, err.Error()) } if len(certs) == 0 { return fmt.Errorf("failed to show details of certificate %s, with error: no valid certificate found in the file", cert) } //write out truststore.ShowCerts(certs) return nil } notation-1.2.0/cmd/notation/cert/show_test.go000066400000000000000000000027111466375446100212670ustar00rootroot00000000000000// Copyright The Notary Project Authors. // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package cert import ( "reflect" "testing" ) func TestCertShowCommand(t *testing.T) { opts := &certShowOpts{} cmd := certShowCommand(opts) expected := &certShowOpts{ storeType: "ca", namedStore: "test", cert: "test.crt", } if err := cmd.ParseFlags([]string{ "test.crt", "-t", "ca", "-s", "test"}); err != nil { t.Fatalf("Parse Flag failed: %v", err) } if err := cmd.Args(cmd, cmd.Flags().Args()); err != nil { t.Fatalf("Parse Args failed: %v", err) } if !reflect.DeepEqual(*expected, *opts) { t.Fatalf("Expect cert show opts: %v, got: %v", expected, opts) } } func TestCertShowCommand_MissingArgs(t *testing.T) { cmd := certShowCommand(nil) if err := cmd.ParseFlags([]string{}); err != nil { t.Fatalf("Parse Flag failed: %v", err) } if err := cmd.Args(cmd, cmd.Flags().Args()); err == nil { t.Fatal("Parse Args expected error, but ok") } } notation-1.2.0/cmd/notation/common.go000066400000000000000000000050711466375446100176050ustar00rootroot00000000000000// Copyright The Notary Project Authors. // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package main import ( "os" "github.com/spf13/pflag" "oras.land/oras-go/v2/registry/remote/auth" ) const ( defaultUsernameEnv = "NOTATION_USERNAME" defaultPasswordEnv = "NOTATION_PASSWORD" defaultMediaType = "application/vnd.docker.distribution.manifest.v2+json" ) var ( flagUsername = &pflag.Flag{ Name: "username", Shorthand: "u", Usage: "username for registry operations (default to $NOTATION_USERNAME if not specified)", } setflagUsername = func(fs *pflag.FlagSet, p *string) { fs.StringVarP(p, flagUsername.Name, flagUsername.Shorthand, "", flagUsername.Usage) } flagPassword = &pflag.Flag{ Name: "password", Shorthand: "p", Usage: "password for registry operations (default to $NOTATION_PASSWORD if not specified)", } setFlagPassword = func(fs *pflag.FlagSet, p *string) { fs.StringVarP(p, flagPassword.Name, flagPassword.Shorthand, "", flagPassword.Usage) } flagInsecureRegistry = &pflag.Flag{ Name: "insecure-registry", Usage: "use HTTP protocol while connecting to registries. Should be used only for testing", DefValue: "false", } setFlagInsecureRegistry = func(fs *pflag.FlagSet, p *bool) { fs.BoolVar(p, flagInsecureRegistry.Name, false, flagInsecureRegistry.Usage) } ) type SecureFlagOpts struct { Username string Password string InsecureRegistry bool } // ApplyFlags set flags and their default values for the FlagSet func (opts *SecureFlagOpts) ApplyFlags(fs *pflag.FlagSet) { setflagUsername(fs, &opts.Username) setFlagPassword(fs, &opts.Password) setFlagInsecureRegistry(fs, &opts.InsecureRegistry) opts.Username = os.Getenv(defaultUsernameEnv) opts.Password = os.Getenv(defaultPasswordEnv) } // Credential returns an auth.Credential from opts.Username and opts.Password. func (opts *SecureFlagOpts) Credential() auth.Credential { if opts.Username == "" { return auth.Credential{ RefreshToken: opts.Password, } } return auth.Credential{ Username: opts.Username, Password: opts.Password, } } notation-1.2.0/cmd/notation/common_test.go000066400000000000000000000032541466375446100206450ustar00rootroot00000000000000// Copyright The Notary Project Authors. // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package main import ( "reflect" "testing" "oras.land/oras-go/v2/registry/remote/auth" ) func TestSecureFlagOpts_Credential(t *testing.T) { tests := []struct { name string opts *SecureFlagOpts want auth.Credential }{ { name: "Username and password", opts: &SecureFlagOpts{ Username: "username", Password: "password", }, want: auth.Credential{ Username: "username", Password: "password", }, }, { name: "Username only", opts: &SecureFlagOpts{ Username: "username", }, want: auth.Credential{ Username: "username", }, }, { name: "Password only", opts: &SecureFlagOpts{ Password: "token", }, want: auth.Credential{ RefreshToken: "token", }, }, { name: "Empty username and password", opts: &SecureFlagOpts{ Username: "", Password: "", }, want: auth.EmptyCredential, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if got := tt.opts.Credential(); !reflect.DeepEqual(got, tt.want) { t.Errorf("SecureFlagOpts.Credential() = %v, want %v", got, tt.want) } }) } } notation-1.2.0/cmd/notation/inspect.go000066400000000000000000000321731466375446100177650ustar00rootroot00000000000000// Copyright The Notary Project Authors. // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package main import ( "crypto/sha256" "crypto/x509" "encoding/hex" "errors" "fmt" "os" "strconv" "strings" "time" "github.com/notaryproject/notation-core-go/signature" "github.com/notaryproject/notation-go/plugin/proto" "github.com/notaryproject/notation-go/registry" cmderr "github.com/notaryproject/notation/cmd/notation/internal/errors" "github.com/notaryproject/notation/cmd/notation/internal/experimental" "github.com/notaryproject/notation/internal/cmd" "github.com/notaryproject/notation/internal/envelope" "github.com/notaryproject/notation/internal/ioutil" "github.com/notaryproject/notation/internal/tree" "github.com/notaryproject/tspclient-go" ocispec "github.com/opencontainers/image-spec/specs-go/v1" "github.com/spf13/cobra" ) type inspectOpts struct { cmd.LoggingFlagOpts SecureFlagOpts reference string outputFormat string allowReferrersAPI bool maxSignatures int } type inspectOutput struct { MediaType string `json:"mediaType"` Signatures []signatureOutput } type signatureOutput struct { MediaType string `json:"mediaType"` Digest string `json:"digest"` SignatureAlgorithm string `json:"signatureAlgorithm"` SignedAttributes map[string]string `json:"signedAttributes"` UserDefinedAttributes map[string]string `json:"userDefinedAttributes"` UnsignedAttributes map[string]any `json:"unsignedAttributes"` Certificates []certificateOutput `json:"certificates"` SignedArtifact ocispec.Descriptor `json:"signedArtifact"` } type certificateOutput struct { SHA256Fingerprint string `json:"SHA256Fingerprint"` IssuedTo string `json:"issuedTo"` IssuedBy string `json:"issuedBy"` Expiry string `json:"expiry"` } type timestampOutput struct { Timestamp string `json:"timestamp,omitempty"` Certificates []certificateOutput `json:"certificates,omitempty"` Error string `json:"error,omitempty"` } func inspectCommand(opts *inspectOpts) *cobra.Command { if opts == nil { opts = &inspectOpts{} } longMessage := `Inspect all signatures associated with the signed artifact. Example - Inspect signatures on an OCI artifact identified by a digest: notation inspect /@ Example - Inspect signatures on an OCI artifact identified by a tag (Notation will resolve tag to digest): notation inspect /: Example - Inspect signatures on an OCI artifact identified by a digest and output as json: notation inspect --output json /@ ` command := &cobra.Command{ Use: "inspect [reference]", Short: "Inspect all signatures associated with the signed artifact", Long: longMessage, Args: func(cmd *cobra.Command, args []string) error { if len(args) == 0 { return errors.New("missing reference to the artifact: use `notation inspect --help` to see what parameters are required") } opts.reference = args[0] return nil }, PreRunE: func(cmd *cobra.Command, args []string) error { return experimental.CheckFlagsAndWarn(cmd, "allow-referrers-api") }, RunE: func(cmd *cobra.Command, args []string) error { if opts.maxSignatures <= 0 { return fmt.Errorf("max-signatures value %d must be a positive number", opts.maxSignatures) } if cmd.Flags().Changed("allow-referrers-api") { fmt.Fprintln(os.Stderr, "Warning: flag '--allow-referrers-api' is deprecated and will be removed in future versions.") } return runInspect(cmd, opts) }, } opts.LoggingFlagOpts.ApplyFlags(command.Flags()) opts.SecureFlagOpts.ApplyFlags(command.Flags()) cmd.SetPflagOutput(command.Flags(), &opts.outputFormat, cmd.PflagOutputUsage) command.Flags().IntVar(&opts.maxSignatures, "max-signatures", 100, "maximum number of signatures to evaluate or examine") cmd.SetPflagReferrersAPI(command.Flags(), &opts.allowReferrersAPI, fmt.Sprintf(cmd.PflagReferrersUsageFormat, "inspect")) return command } func runInspect(command *cobra.Command, opts *inspectOpts) error { // set log level ctx := opts.LoggingFlagOpts.InitializeLogger(command.Context()) if opts.outputFormat != cmd.OutputJSON && opts.outputFormat != cmd.OutputPlaintext { return fmt.Errorf("unrecognized output format %s", opts.outputFormat) } // initialize reference := opts.reference // always use the Referrers API, if not supported, automatically fallback to // the referrers tag schema sigRepo, err := getRemoteRepository(ctx, &opts.SecureFlagOpts, reference, false) if err != nil { return err } manifestDesc, resolvedRef, err := resolveReferenceWithWarning(ctx, inputTypeRegistry, reference, sigRepo, "inspect") if err != nil { return err } output := inspectOutput{MediaType: manifestDesc.MediaType, Signatures: []signatureOutput{}} skippedSignatures := false err = listSignatures(ctx, sigRepo, manifestDesc, opts.maxSignatures, func(sigManifestDesc ocispec.Descriptor) error { sigBlob, sigDesc, err := sigRepo.FetchSignatureBlob(ctx, sigManifestDesc) if err != nil { fmt.Fprintf(os.Stderr, "Warning: unable to fetch signature %s due to error: %v\n", sigManifestDesc.Digest.String(), err) skippedSignatures = true return nil } sigEnvelope, err := signature.ParseEnvelope(sigDesc.MediaType, sigBlob) if err != nil { logSkippedSignature(sigManifestDesc, err) skippedSignatures = true return nil } envelopeContent, err := sigEnvelope.Content() if err != nil { logSkippedSignature(sigManifestDesc, err) skippedSignatures = true return nil } signedArtifactDesc, err := envelope.DescriptorFromSignaturePayload(&envelopeContent.Payload) if err != nil { logSkippedSignature(sigManifestDesc, err) skippedSignatures = true return nil } signatureAlgorithm, err := proto.EncodeSigningAlgorithm(envelopeContent.SignerInfo.SignatureAlgorithm) if err != nil { logSkippedSignature(sigManifestDesc, err) skippedSignatures = true return nil } sig := signatureOutput{ MediaType: sigDesc.MediaType, Digest: sigManifestDesc.Digest.String(), SignatureAlgorithm: string(signatureAlgorithm), SignedAttributes: getSignedAttributes(opts.outputFormat, envelopeContent), UserDefinedAttributes: signedArtifactDesc.Annotations, UnsignedAttributes: getUnsignedAttributes(opts.outputFormat, envelopeContent), Certificates: getCertificates(opts.outputFormat, envelopeContent.SignerInfo.CertificateChain), SignedArtifact: *signedArtifactDesc, } // clearing annotations from the SignedArtifact field since they're already // displayed as UserDefinedAttributes sig.SignedArtifact.Annotations = nil output.Signatures = append(output.Signatures, sig) return nil }) var errorExceedMaxSignatures cmderr.ErrorExceedMaxSignatures if err != nil && !errors.As(err, &errorExceedMaxSignatures) { return err } if err := printOutput(opts.outputFormat, resolvedRef, output); err != nil { return err } if errorExceedMaxSignatures.MaxSignatures > 0 { fmt.Println("Warning:", errorExceedMaxSignatures) } if skippedSignatures { return errors.New("at least one signature was skipped and not displayed") } return nil } func logSkippedSignature(sigDesc ocispec.Descriptor, err error) { fmt.Fprintf(os.Stderr, "Warning: Skipping signature %s because of error: %v\n", sigDesc.Digest.String(), err) } func getSignedAttributes(outputFormat string, envContent *signature.EnvelopeContent) map[string]string { signedAttributes := map[string]string{ "signingScheme": string(envContent.SignerInfo.SignedAttributes.SigningScheme), "signingTime": formatTimestamp(outputFormat, envContent.SignerInfo.SignedAttributes.SigningTime), } expiry := envContent.SignerInfo.SignedAttributes.Expiry if !expiry.IsZero() { signedAttributes["expiry"] = formatTimestamp(outputFormat, expiry) } for _, attribute := range envContent.SignerInfo.SignedAttributes.ExtendedAttributes { signedAttributes[fmt.Sprint(attribute.Key)] = fmt.Sprint(attribute.Value) } return signedAttributes } func getUnsignedAttributes(outputFormat string, envContent *signature.EnvelopeContent) map[string]any { unsignedAttributes := make(map[string]any) if envContent.SignerInfo.UnsignedAttributes.TimestampSignature != nil { unsignedAttributes["timestampSignature"] = parseTimestamp(outputFormat, envContent.SignerInfo) } if envContent.SignerInfo.UnsignedAttributes.SigningAgent != "" { unsignedAttributes["signingAgent"] = envContent.SignerInfo.UnsignedAttributes.SigningAgent } return unsignedAttributes } func formatTimestamp(outputFormat string, t time.Time) string { switch outputFormat { case cmd.OutputJSON: return t.Format(time.RFC3339) default: return t.Format(time.ANSIC) } } func getCertificates(outputFormat string, certChain []*x509.Certificate) []certificateOutput { certificates := []certificateOutput{} for _, cert := range certChain { h := sha256.Sum256(cert.Raw) fingerprint := strings.ToLower(hex.EncodeToString(h[:])) certificate := certificateOutput{ SHA256Fingerprint: fingerprint, IssuedTo: cert.Subject.String(), IssuedBy: cert.Issuer.String(), Expiry: formatTimestamp(outputFormat, cert.NotAfter), } certificates = append(certificates, certificate) } return certificates } func printOutput(outputFormat string, ref string, output inspectOutput) error { if outputFormat == cmd.OutputJSON { return ioutil.PrintObjectAsJSON(output) } if len(output.Signatures) == 0 { fmt.Printf("%s has no associated signature\n", ref) return nil } fmt.Println("Inspecting all signatures for signed artifact") root := tree.New(ref) cncfSigNode := root.Add(registry.ArtifactTypeNotation) for _, signature := range output.Signatures { sigNode := cncfSigNode.Add(signature.Digest) sigNode.AddPair("media type", signature.MediaType) sigNode.AddPair("signature algorithm", signature.SignatureAlgorithm) signedAttributesNode := sigNode.Add("signed attributes") addMapToTree(signedAttributesNode, signature.SignedAttributes) userDefinedAttributesNode := sigNode.Add("user defined attributes") addMapToTree(userDefinedAttributesNode, signature.UserDefinedAttributes) unsignedAttributesNode := sigNode.Add("unsigned attributes") for k, v := range signature.UnsignedAttributes { switch value := v.(type) { case string: unsignedAttributesNode.AddPair(k, value) case timestampOutput: timestampNode := unsignedAttributesNode.Add("timestamp signature") if value.Error != "" { timestampNode.AddPair("error", value.Error) break } timestampNode.AddPair("timestamp", value.Timestamp) addCertificatesToTree(timestampNode, "certificates", value.Certificates) } } addCertificatesToTree(sigNode, "certificates", signature.Certificates) artifactNode := sigNode.Add("signed artifact") artifactNode.AddPair("media type", signature.SignedArtifact.MediaType) artifactNode.AddPair("digest", signature.SignedArtifact.Digest.String()) artifactNode.AddPair("size", strconv.FormatInt(signature.SignedArtifact.Size, 10)) } root.Print() return nil } func addMapToTree(node *tree.Node, m map[string]string) { if len(m) > 0 { for k, v := range m { node.AddPair(k, v) } } else { node.Add("(empty)") } } func addCertificatesToTree(node *tree.Node, name string, certs []certificateOutput) { certListNode := node.Add(name) for _, cert := range certs { certNode := certListNode.AddPair("SHA256 fingerprint", cert.SHA256Fingerprint) certNode.AddPair("issued to", cert.IssuedTo) certNode.AddPair("issued by", cert.IssuedBy) certNode.AddPair("expiry", cert.Expiry) } } func parseTimestamp(outputFormat string, signerInfo signature.SignerInfo) timestampOutput { signedToken, err := tspclient.ParseSignedToken(signerInfo.UnsignedAttributes.TimestampSignature) if err != nil { return timestampOutput{ Error: fmt.Sprintf("failed to parse timestamp countersignature: %s", err.Error()), } } info, err := signedToken.Info() if err != nil { return timestampOutput{ Error: fmt.Sprintf("failed to parse timestamp countersignature: %s", err.Error()), } } timestamp, err := info.Validate(signerInfo.Signature) if err != nil { return timestampOutput{ Error: fmt.Sprintf("failed to parse timestamp countersignature: %s", err.Error()), } } certificates := getCertificates(outputFormat, signedToken.Certificates) var formatTimestamp string switch outputFormat { case cmd.OutputJSON: formatTimestamp = timestamp.Format(time.RFC3339) default: formatTimestamp = timestamp.Format(time.ANSIC) } return timestampOutput{ Timestamp: formatTimestamp, Certificates: certificates, } } notation-1.2.0/cmd/notation/inspect_test.go000066400000000000000000000064571466375446100210320ustar00rootroot00000000000000// Copyright The Notary Project Authors. // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package main import ( "testing" "github.com/notaryproject/notation-core-go/signature" "github.com/notaryproject/notation/internal/cmd" ) func TestInspectCommand_SecretsFromArgs(t *testing.T) { opts := &inspectOpts{} command := inspectCommand(opts) expected := &inspectOpts{ reference: "ref", SecureFlagOpts: SecureFlagOpts{ Password: "password", InsecureRegistry: true, Username: "user", }, outputFormat: cmd.OutputPlaintext, maxSignatures: 100, } if err := command.ParseFlags([]string{ "--password", expected.Password, expected.reference, "-u", expected.Username, "--insecure-registry", "--output", "text"}); err != nil { t.Fatalf("Parse Flag failed: %v", err) } if err := command.Args(command, command.Flags().Args()); err != nil { t.Fatalf("Parse Args failed: %v", err) } if *opts != *expected { t.Fatalf("Expect inspect opts: %v, got: %v", expected, opts) } } func TestInspectCommand_SecretsFromEnv(t *testing.T) { t.Setenv(defaultUsernameEnv, "user") t.Setenv(defaultPasswordEnv, "password") opts := &inspectOpts{} expected := &inspectOpts{ reference: "ref", SecureFlagOpts: SecureFlagOpts{ Password: "password", Username: "user", }, outputFormat: cmd.OutputJSON, maxSignatures: 100, } command := inspectCommand(opts) if err := command.ParseFlags([]string{ expected.reference, "--output", "json"}); err != nil { t.Fatalf("Parse Flag failed: %v", err) } if err := command.Args(command, command.Flags().Args()); err != nil { t.Fatalf("Parse Args failed: %v", err) } if *opts != *expected { t.Fatalf("Expect inspect opts: %v, got: %v", expected, opts) } } func TestInspectCommand_MissingArgs(t *testing.T) { command := inspectCommand(nil) if err := command.ParseFlags(nil); err != nil { t.Fatalf("Parse Flag failed: %v", err) } if err := command.Args(command, command.Flags().Args()); err == nil { t.Fatal("Parse Args expected error, but ok") } } func TestGetUnsignedAttributes(t *testing.T) { envContent := &signature.EnvelopeContent{ SignerInfo: signature.SignerInfo{ UnsignedAttributes: signature.UnsignedAttributes{ TimestampSignature: []byte("invalid"), }, }, } expectedErrMsg := "failed to parse timestamp countersignature: cms: syntax error: invalid signed data: failed to convert from BER to DER: asn1: syntax error: decoding BER length octets: short form length octets value should be less or equal to the subsequent octets length" unsignedAttr := getUnsignedAttributes(cmd.OutputPlaintext, envContent) val, ok := unsignedAttr["timestampSignature"].(timestampOutput) if !ok { t.Fatal("expected to have timestampSignature") } if val.Error != expectedErrMsg { t.Fatalf("expected %s, but got %s", expectedErrMsg, val.Error) } } notation-1.2.0/cmd/notation/internal/000077500000000000000000000000001466375446100175775ustar00rootroot00000000000000notation-1.2.0/cmd/notation/internal/cmdutil/000077500000000000000000000000001466375446100212405ustar00rootroot00000000000000notation-1.2.0/cmd/notation/internal/cmdutil/confirmation.go000066400000000000000000000022631466375446100242620ustar00rootroot00000000000000// Copyright The Notary Project Authors. // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package cmdutil import ( "bufio" "fmt" "io" "strings" ) // AskForConfirmation prints a propmt to ask for confirmation before doing an // action and takes user input as response. func AskForConfirmation(r io.Reader, prompt string, confirmed bool) (bool, error) { if confirmed { return true, nil } fmt.Print(prompt, " [y/N] ") scanner := bufio.NewScanner(r) if ok := scanner.Scan(); !ok { return false, scanner.Err() } response := scanner.Text() switch strings.ToLower(response) { case "y", "yes": return true, nil default: fmt.Println("Operation cancelled.") return false, nil } } notation-1.2.0/cmd/notation/internal/errors/000077500000000000000000000000001466375446100211135ustar00rootroot00000000000000notation-1.2.0/cmd/notation/internal/errors/errors.go000066400000000000000000000030651466375446100227620ustar00rootroot00000000000000// Copyright The Notary Project Authors. // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package errors import "fmt" // ErrorReferrersAPINotSupported is used when the target registry does not // support the Referrers API type ErrorReferrersAPINotSupported struct { Msg string } func (e ErrorReferrersAPINotSupported) Error() string { if e.Msg != "" { return e.Msg } return "referrers API not supported" } // ErrorOCILayoutMissingReference is used when signing local content in oci // layout folder but missing input tag or digest. type ErrorOCILayoutMissingReference struct { Msg string } func (e ErrorOCILayoutMissingReference) Error() string { if e.Msg != "" { return e.Msg } return "reference is missing either digest or tag" } // ErrorExceedMaxSignatures is used when the number of signatures has surpassed // the maximum limit that can be evaluated. type ErrorExceedMaxSignatures struct { MaxSignatures int } func (e ErrorExceedMaxSignatures) Error() string { return fmt.Sprintf("exceeded configured limit of max signatures %d to examine", e.MaxSignatures) } notation-1.2.0/cmd/notation/internal/experimental/000077500000000000000000000000001466375446100222745ustar00rootroot00000000000000notation-1.2.0/cmd/notation/internal/experimental/experimental.go000066400000000000000000000054451466375446100253300ustar00rootroot00000000000000// Copyright The Notary Project Authors. // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package experimental import ( "fmt" "os" "strings" "github.com/spf13/cobra" ) const ( envName = "NOTATION_EXPERIMENTAL" enabled = "1" ) // IsDisabled determines whether experimental features are disabled. func IsDisabled() bool { return os.Getenv(envName) != enabled } // CheckCommandAndWarn checks whether an experimental command can be run. func CheckCommandAndWarn(cmd *cobra.Command, _ []string) error { return CheckAndWarn(func() (string, bool) { return fmt.Sprintf("%q", cmd.CommandPath()), true }) } // CheckFlagsAndWarn checks whether experimental flags can be run. func CheckFlagsAndWarn(cmd *cobra.Command, flags ...string) error { return CheckAndWarn(func() (string, bool) { var changedFlags []string flagSet := cmd.Flags() for _, flag := range flags { if flagSet.Changed(flag) { changedFlags = append(changedFlags, "--"+flag) } } if len(changedFlags) == 0 { // no experimental flag used return "", false } return fmt.Sprintf("flag(s) %s in %q", strings.Join(changedFlags, ","), cmd.CommandPath()), true }) } // CheckAndWarn checks whether a feature can be used. func CheckAndWarn(doCheck func() (feature string, isExperimental bool)) error { feature, isExperimental := doCheck() if isExperimental { if IsDisabled() { // feature is experimental and disabled return fmt.Errorf("%s is experimental and not enabled by default. To use, please set %s=%s environment variable", feature, envName, enabled) } return warn() } return nil } // warn prints a warning message for using the experimental feature. func warn() error { _, err := fmt.Fprintf(os.Stderr, "Warning: This feature is experimental and may not be fully tested or completed and may be deprecated. Report any issues to \"https://github/notaryproject/notation\"\n") return err } // HideFlags hides experimental flags when NOTATION_EXPERIMENTAL is disabled // and updates the command's long message accordingly when NOTATION_EXPERIMENTAL // is enabled. func HideFlags(cmd *cobra.Command, experimentalExamples string, flags []string) { if IsDisabled() { flagsSet := cmd.Flags() for _, flag := range flags { flagsSet.MarkHidden(flag) } } else if experimentalExamples != "" { cmd.Long += experimentalExamples } } notation-1.2.0/cmd/notation/internal/plugin/000077500000000000000000000000001466375446100210755ustar00rootroot00000000000000notation-1.2.0/cmd/notation/internal/plugin/plugin.go000066400000000000000000000047741466375446100227360ustar00rootroot00000000000000// Copyright The Notary Project Authors. // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package plugin import ( "context" "fmt" "io" "net/http" "time" "github.com/notaryproject/notation/internal/httputil" ) // MaxPluginSourceBytes specifies the limit on how many bytes are allowed in the // server's response to the download from URL request. // // The plugin source size must be strictly less than this value. var MaxPluginSourceBytes int64 = 256 * 1024 * 1024 // 256 MiB // PluginSourceType is an enum for plugin source type PluginSourceType int const ( // PluginSourceTypeFile means plugin source is file PluginSourceTypeFile PluginSourceType = 1 + iota // PluginSourceTypeURL means plugin source is URL PluginSourceTypeURL ) const ( // MediaTypeZip means plugin file is zip MediaTypeZip = "application/zip" // MediaTypeGzip means plugin file is gzip MediaTypeGzip = "application/x-gzip" ) // DownloadPluginFromURLTimeout is the timeout when downloading plugin from a // URL const DownloadPluginFromURLTimeout = 10 * time.Minute // DownloadPluginFromURL downloads plugin source from url to a tmp file on file // system func DownloadPluginFromURL(ctx context.Context, pluginURL string, tmpFile io.Writer) error { // Get the data client := httputil.NewAuthClient(ctx, &http.Client{Timeout: DownloadPluginFromURLTimeout}) req, err := http.NewRequest(http.MethodGet, pluginURL, nil) if err != nil { return err } resp, err := client.Do(req) if err != nil { return err } defer resp.Body.Close() // Check server response if resp.StatusCode != http.StatusOK { return fmt.Errorf("%s %q: https response bad status: %s", resp.Request.Method, resp.Request.URL, resp.Status) } // Write the body to file lr := &io.LimitedReader{ R: resp.Body, N: MaxPluginSourceBytes, } _, err = io.Copy(tmpFile, lr) if err != nil { return err } if lr.N == 0 { return fmt.Errorf("%s %q: https response reached the %d MiB size limit", resp.Request.Method, resp.Request.URL, MaxPluginSourceBytes/1024/1024) } return nil } notation-1.2.0/cmd/notation/internal/truststore/000077500000000000000000000000001466375446100220355ustar00rootroot00000000000000notation-1.2.0/cmd/notation/internal/truststore/truststore.go000066400000000000000000000142531466375446100246270ustar00rootroot00000000000000// Copyright The Notary Project Authors. // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package truststore import ( "crypto/sha256" "crypto/x509" "encoding/hex" "errors" "fmt" "io/fs" "os" "path/filepath" "regexp" "strings" corex509 "github.com/notaryproject/notation-core-go/x509" "github.com/notaryproject/notation-go/dir" "github.com/notaryproject/notation-go/verifier/truststore" "github.com/notaryproject/notation/cmd/notation/internal/cmdutil" "github.com/notaryproject/notation/internal/osutil" ) // AddCert adds a single cert file at path to the trust store // under dir truststore/x509/storeType/namedStore func AddCert(path, storeType, namedStore string, display bool) error { // initialize certPath, err := filepath.Abs(path) if err != nil { return err } if storeType == "" { return errors.New("store type cannot be empty") } if !IsValidStoreType(storeType) { return fmt.Errorf("unsupported store type: %s", storeType) } if !IsValidFileName(namedStore) { return errors.New("named store name needs to follow [a-zA-Z0-9_.-]+ format") } // check if the target path is a x509 certificate // (support PEM and DER formats) certs, err := corex509.ReadCertificateFile(certPath) if err != nil { return err } if len(certs) == 0 { return errors.New("no valid certificate found in the file") } // core process // get the trust store path trustStorePath, err := dir.ConfigFS().SysPath(dir.TrustStoreDir, "x509", storeType, namedStore) if err := CheckNonErrNotExistError(err); err != nil { return err } // check if certificate already in the trust store if _, err := os.Stat(filepath.Join(trustStorePath, filepath.Base(certPath))); err == nil { return errors.New("certificate already exists in the Trust Store") } // add cert to trust store _, err = osutil.CopyToDir(certPath, trustStorePath) if err != nil { return err } // write out if display { fmt.Printf("Successfully added %s to named store %s of type %s\n", filepath.Base(certPath), namedStore, storeType) } return nil } // ListCerts walks through root and returns all x509 certificates in it, // sub-dirs are ignored. func ListCerts(root string, depth int) ([]string, error) { maxDepth := strings.Count(root, string(os.PathSeparator)) + depth var certPaths []string if err := filepath.WalkDir(root, func(path string, d fs.DirEntry, err error) error { if err != nil { return err } if d.IsDir() && strings.Count(path, string(os.PathSeparator)) > maxDepth { return fs.SkipDir } info, err := d.Info() if err != nil { return err } if info.Mode().IsRegular() { certs, err := corex509.ReadCertificateFile(path) if err != nil { return err } if len(certs) != 0 { certPaths = append(certPaths, path) } } return nil }); err != nil { return nil, err } return certPaths, nil } // ShowCerts writes out details of certificates func ShowCerts(certs []*x509.Certificate) { fmt.Println("Certificate details") fmt.Println("--------------------------------------------------------------------------------") for ind, cert := range certs { showCert(cert) if ind != len(certs)-1 { fmt.Println("--------------------------------------------------------------------------------") } } } // showCert displays details of a certificate func showCert(cert *x509.Certificate) { fmt.Println("Issuer:", cert.Issuer) fmt.Println("Subject:", cert.Subject) fmt.Println("Valid from:", cert.NotBefore) fmt.Println("Valid to:", cert.NotAfter) fmt.Println("IsCA:", cert.IsCA) h := sha256.Sum256(cert.Raw) fmt.Println("SHA256 Thumbprint:", strings.ToLower(hex.EncodeToString(h[:]))) } // DeleteAllCerts deletes all certificate files from the trust store // under dir truststore/x509/storeType/namedStore func DeleteAllCerts(storeType, namedStore string, confirmed bool) error { path, err := dir.ConfigFS().SysPath(dir.TrustStoreDir, "x509", storeType, namedStore) if err != nil { return err } prompt := fmt.Sprintf("Are you sure you want to delete all certificates in %q of type %q?", namedStore, storeType) confirmed, err = cmdutil.AskForConfirmation(os.Stdin, prompt, confirmed) if err != nil { return err } if !confirmed { return nil } if _, err = os.Stat(path); err != nil { return err } if err = os.RemoveAll(path); err != nil { return err } // write out on success fmt.Printf("Successfully deleted %s\n", path) return nil } // DeleteCert deletes a specific certificate file from the // trust store, namely truststore/x509/storeType/namedStore/cert func DeleteCert(storeType, namedStore, cert string, confirmed bool) error { path, err := dir.ConfigFS().SysPath(dir.TrustStoreDir, "x509", storeType, namedStore, cert) if err != nil { return err } prompt := fmt.Sprintf("Are you sure you want to delete %q in %q of type %q?", cert, namedStore, storeType) confirmed, err = cmdutil.AskForConfirmation(os.Stdin, prompt, confirmed) if err != nil { return err } if !confirmed { return nil } if _, err := os.Stat(path); err != nil { return err } if err = os.Remove(path); err != nil { return err } // write out on success fmt.Printf("Successfully deleted %s\n", cert) return nil } // CheckNonErrNotExistError returns nil when no err or err is fs.ErrNotExist func CheckNonErrNotExistError(err error) error { if err != nil && !errors.Is(err, fs.ErrNotExist) { return err } return nil } // IsValidStoreType checks if storeType is supported func IsValidStoreType(storeType string) bool { for _, t := range truststore.Types { if storeType == string(t) { return true } } return false } // IsValidFileName checks if a file name is cross-platform compatible func IsValidFileName(fileName string) bool { return regexp.MustCompile(`^[a-zA-Z0-9_.-]+$`).MatchString(fileName) } notation-1.2.0/cmd/notation/internal/truststore/truststore_test.go000066400000000000000000000020061466375446100256570ustar00rootroot00000000000000// Copyright The Notary Project Authors. // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package truststore import ( "errors" "path/filepath" "testing" ) func TestEmptyCertFile(t *testing.T) { path := filepath.FromSlash("../../../../internal/testdata/Empty.txt") expectedErr := errors.New("no valid certificate found in the empty file") err := AddCert(path, "ca", "test", false) if err == nil || err.Error() != "no valid certificate found in the file" { t.Fatalf("expected err: %v, got: %v", expectedErr, err) } } notation-1.2.0/cmd/notation/key.go000066400000000000000000000156001466375446100171040ustar00rootroot00000000000000// Copyright The Notary Project Authors. // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package main import ( "context" "errors" "fmt" "os" "github.com/notaryproject/notation-go/config" "github.com/notaryproject/notation-go/log" "github.com/notaryproject/notation/internal/cmd" "github.com/notaryproject/notation/internal/ioutil" "github.com/spf13/cobra" "github.com/spf13/pflag" ) var ( keyDefaultFlag = &pflag.Flag{ Name: "default", Usage: "mark as default", } setKeyDefaultFlag = func(fs *pflag.FlagSet, p *bool) { fs.BoolVarP(p, keyDefaultFlag.Name, keyDefaultFlag.Shorthand, false, keyDefaultFlag.Usage) } ) type keyAddOpts struct { cmd.LoggingFlagOpts name string plugin string id string pluginConfig []string isDefault bool } type keyUpdateOpts struct { cmd.LoggingFlagOpts name string isDefault bool } type keyDeleteOpts struct { cmd.LoggingFlagOpts names []string } func keyCommand() *cobra.Command { command := &cobra.Command{ Use: "key", Short: "Manage keys used for signing", Long: `Manage keys used for signing Example - Add a key to signing key list: notation key add --plugin --id Example - List keys used for signing: notation key ls Example - Update the default signing key: notation key set --default Example - Delete the key from signing key list: notation key delete ... `, } command.AddCommand(keyAddCommand(nil), keyUpdateCommand(nil), keyListCommand(), keyDeleteCommand(nil)) return command } func keyAddCommand(opts *keyAddOpts) *cobra.Command { if opts == nil { opts = &keyAddOpts{} } command := &cobra.Command{ Use: "add --plugin [flags] ", Short: "Add key to Notation signing key list", Args: func(cmd *cobra.Command, args []string) error { if len(args) != 1 { return errors.New("either missing key name or unnecessary parameters passed") } opts.name = args[0] return nil }, RunE: func(cmd *cobra.Command, args []string) error { return addKey(cmd.Context(), opts) }, } opts.LoggingFlagOpts.ApplyFlags(command.Flags()) command.Flags().StringVar(&opts.plugin, "plugin", "", "signing plugin name") command.MarkFlagRequired("plugin") command.Flags().StringVar(&opts.id, "id", "", "key id (required if --plugin is set)") cmd.SetPflagPluginConfig(command.Flags(), &opts.pluginConfig) setKeyDefaultFlag(command.Flags(), &opts.isDefault) return command } func keyUpdateCommand(opts *keyUpdateOpts) *cobra.Command { if opts == nil { opts = &keyUpdateOpts{} } command := &cobra.Command{ Use: "update [flags] ", Aliases: []string{"set"}, Short: "Update key in Notation signing key list", Args: func(cmd *cobra.Command, args []string) error { if len(args) == 0 { return errors.New("missing key name") } opts.name = args[0] return nil }, RunE: func(cmd *cobra.Command, args []string) error { return updateKey(cmd.Context(), opts) }, } opts.LoggingFlagOpts.ApplyFlags(command.Flags()) setKeyDefaultFlag(command.Flags(), &opts.isDefault) return command } func keyListCommand() *cobra.Command { return &cobra.Command{ Use: "list [flags]", Aliases: []string{"ls"}, Short: "List keys used for signing", RunE: func(cmd *cobra.Command, args []string) error { return listKeys() }, } } func keyDeleteCommand(opts *keyDeleteOpts) *cobra.Command { if opts == nil { opts = &keyDeleteOpts{} } command := &cobra.Command{ Use: "delete [flags] ...", Short: "Remove key from Notation signing key list", Args: func(cmd *cobra.Command, args []string) error { if len(args) == 0 { return errors.New("missing key names") } opts.names = args return nil }, RunE: func(cmd *cobra.Command, args []string) error { return deleteKeys(cmd.Context(), opts) }, } opts.LoggingFlagOpts.ApplyFlags(command.Flags()) return command } func addKey(ctx context.Context, opts *keyAddOpts) error { // set log level ctx = opts.LoggingFlagOpts.InitializeLogger(ctx) pluginConfig, err := cmd.ParseFlagMap(opts.pluginConfig, cmd.PflagPluginConfig.Name) if err != nil { return err } // core process exec := func(s *config.SigningKeys) error { return s.AddPlugin(ctx, opts.name, opts.id, opts.plugin, pluginConfig, opts.isDefault) } if err := config.LoadExecSaveSigningKeys(exec); err != nil { return err } if opts.isDefault { fmt.Printf("%s: marked as default\n", opts.name) } else { fmt.Println(opts.name) } return nil } func updateKey(ctx context.Context, opts *keyUpdateOpts) error { // set log level ctx = opts.LoggingFlagOpts.InitializeLogger(ctx) logger := log.GetLogger(ctx) if !opts.isDefault { logger.Warn("--default flag is not set, command did not take effect") return nil } // core process exec := func(s *config.SigningKeys) error { return s.UpdateDefault(opts.name) } if err := config.LoadExecSaveSigningKeys(exec); err != nil { return err } // write out fmt.Printf("%s: marked as default\n", opts.name) return nil } func listKeys() error { // core process signingKeys, err := config.LoadSigningKeys() if err != nil { return err } // write out return ioutil.PrintKeyMap(os.Stdout, signingKeys.Default, signingKeys.Keys) } func deleteKeys(ctx context.Context, opts *keyDeleteOpts) error { // set log level ctx = opts.LoggingFlagOpts.InitializeLogger(ctx) logger := log.GetLogger(ctx) // core process var deletedNames []string var prevDefault string exec := func(s *config.SigningKeys) error { if s.Default != nil { prevDefault = *s.Default } var err error deletedNames, err = s.Remove(opts.names...) if err != nil { logger.Errorf("Keys deletion failed to complete with error: %v", err) } return err } if err := config.LoadExecSaveSigningKeys(exec); err != nil { return err } // write out if len(deletedNames) == 1 { name := deletedNames[0] if name == prevDefault { fmt.Printf("Removed default key %s from Notation signing key list. The source key still exists.\n", name) } else { fmt.Printf("Removed %s from Notation signing key list. The source key still exists.\n", name) } } else if len(deletedNames) > 1 { fmt.Println("Removed the following keys from Notation signing key list. The source keys still exist.") for _, name := range deletedNames { if name == prevDefault { fmt.Println(name, "(default)") } else { fmt.Println(name) } } } return nil } notation-1.2.0/cmd/notation/key_test.go000066400000000000000000000055131466375446100201450ustar00rootroot00000000000000// Copyright The Notary Project Authors. // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package main import ( "reflect" "testing" ) func TestKeyAddCommand_BasicArgs(t *testing.T) { opts := &keyAddOpts{} cmd := keyAddCommand(opts) expected := &keyAddOpts{ name: "name", plugin: "pluginname", id: "pluginid", pluginConfig: []string{"pluginconfig"}, } if err := cmd.ParseFlags([]string{ "--plugin", expected.plugin, "--id", expected.id, "--plugin-config", "pluginconfig", expected.name}); err != nil { t.Fatalf("Parse Flag failed: %v", err) } if err := cmd.Args(cmd, cmd.Flags().Args()); err != nil { t.Fatalf("Parse Args failed: %v", err) } if !reflect.DeepEqual(*expected, *opts) { t.Fatalf("Expect key add opts: %v, got: %v", expected, opts) } } func TestKeyUpdateCommand_BasicArgs(t *testing.T) { opts := &keyUpdateOpts{} cmd := keyUpdateCommand(opts) expected := &keyUpdateOpts{ name: "name", isDefault: true, } if err := cmd.ParseFlags([]string{ expected.name, "--default"}); err != nil { t.Fatalf("Parse Flag failed: %v", err) } if err := cmd.Args(cmd, cmd.Flags().Args()); err != nil { t.Fatalf("Parse Args failed: %v", err) } if *expected != *opts { t.Fatalf("Expect key update opts: %v, got: %v", expected, opts) } } func TestKeyUpdateCommand_MissingArgs(t *testing.T) { cmd := keyUpdateCommand(nil) if err := cmd.ParseFlags(nil); err != nil { t.Fatalf("Parse Flag failed: %v", err) } if err := cmd.Args(cmd, cmd.Flags().Args()); err == nil { t.Fatal("Parse Args expected error, but ok") } } func TestKeyRemoveCommand_BasicArgs(t *testing.T) { opts := &keyDeleteOpts{} cmd := keyDeleteCommand(opts) expected := &keyDeleteOpts{ names: []string{"key0", "key1", "key2"}, } if err := cmd.ParseFlags(expected.names); err != nil { t.Fatalf("Parse Flag failed: %v", err) } if err := cmd.Args(cmd, cmd.Flags().Args()); err != nil { t.Fatalf("Parse Args failed: %v", err) } if !reflect.DeepEqual(*expected, *opts) { t.Fatalf("Expect key remove opts: %v, got: %v", expected, opts) } } func TestKeyRemoveCommand_MissingArgs(t *testing.T) { cmd := keyDeleteCommand(nil) if err := cmd.ParseFlags(nil); err != nil { t.Fatalf("Parse Flag failed: %v", err) } if err := cmd.Args(cmd, cmd.Flags().Args()); err == nil { t.Fatal("Parse Args expected error, but ok") } } notation-1.2.0/cmd/notation/list.go000066400000000000000000000141541466375446100172720ustar00rootroot00000000000000// Copyright The Notary Project Authors. // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package main import ( "context" "errors" "fmt" "os" notationregistry "github.com/notaryproject/notation-go/registry" cmderr "github.com/notaryproject/notation/cmd/notation/internal/errors" "github.com/notaryproject/notation/cmd/notation/internal/experimental" "github.com/notaryproject/notation/internal/cmd" "github.com/opencontainers/go-digest" ocispec "github.com/opencontainers/image-spec/specs-go/v1" "github.com/spf13/cobra" ) type listOpts struct { cmd.LoggingFlagOpts SecureFlagOpts reference string allowReferrersAPI bool ociLayout bool inputType inputType maxSignatures int } func listCommand(opts *listOpts) *cobra.Command { if opts == nil { opts = &listOpts{ inputType: inputTypeRegistry, // remote registry by default } } longMessage := `List all the signatures associated with signed artifact Example - List signatures of an OCI artifact: notation list /@ Example - List signatures of an OCI artifact identified by a tag (Notation will resolve tag to digest) notation list /: ` experimentalExamples := ` Example - [Experimental] List signatures of an OCI artifact referenced in an OCI layout notation list --oci-layout "@" Example - [Experimental] List signatures of an OCI artifact identified by a tag and referenced in an OCI layout notation list --oci-layout ":" ` command := &cobra.Command{ Use: "list [flags] ", Aliases: []string{"ls"}, Short: "List signatures of the signed artifact", Long: longMessage, Args: func(cmd *cobra.Command, args []string) error { if len(args) == 0 { return errors.New("missing reference to the artifact: use `notation list --help` to see what parameters are required") } opts.reference = args[0] return nil }, PreRunE: func(cmd *cobra.Command, args []string) error { if opts.ociLayout { opts.inputType = inputTypeOCILayout } return experimental.CheckFlagsAndWarn(cmd, "allow-referrers-api", "oci-layout") }, RunE: func(cmd *cobra.Command, args []string) error { if opts.maxSignatures <= 0 { return fmt.Errorf("max-signatures value %d must be a positive number", opts.maxSignatures) } if cmd.Flags().Changed("allow-referrers-api") { fmt.Fprintln(os.Stderr, "Warning: flag '--allow-referrers-api' is deprecated and will be removed in future versions.") } return runList(cmd.Context(), opts) }, } opts.LoggingFlagOpts.ApplyFlags(command.Flags()) opts.SecureFlagOpts.ApplyFlags(command.Flags()) cmd.SetPflagReferrersAPI(command.Flags(), &opts.allowReferrersAPI, fmt.Sprintf(cmd.PflagReferrersUsageFormat, "list")) command.Flags().BoolVar(&opts.ociLayout, "oci-layout", false, "[Experimental] list signatures stored in OCI image layout") command.Flags().IntVar(&opts.maxSignatures, "max-signatures", 100, "maximum number of signatures to evaluate or examine") experimental.HideFlags(command, experimentalExamples, []string{"oci-layout"}) return command } func runList(ctx context.Context, opts *listOpts) error { // set log level ctx = opts.LoggingFlagOpts.InitializeLogger(ctx) // initialize reference := opts.reference // always use the Referrers API, if not supported, automatically fallback to // the referrers tag schema sigRepo, err := getRepository(ctx, opts.inputType, reference, &opts.SecureFlagOpts, false) if err != nil { return err } targetDesc, resolvedRef, err := resolveReferenceWithWarning(ctx, opts.inputType, reference, sigRepo, "list") if err != nil { return err } // print all signature manifest digests return printSignatureManifestDigests(ctx, targetDesc, sigRepo, resolvedRef, opts.maxSignatures) } // printSignatureManifestDigests returns the signature manifest digests of // the subject manifest. func printSignatureManifestDigests(ctx context.Context, targetDesc ocispec.Descriptor, sigRepo notationregistry.Repository, ref string, maxSigs int) error { titlePrinted := false printTitle := func() { if !titlePrinted { fmt.Println(ref) fmt.Printf("└── %s\n", notationregistry.ArtifactTypeNotation) titlePrinted = true } } var prevDigest digest.Digest err := listSignatures(ctx, sigRepo, targetDesc, maxSigs, func(sigManifestDesc ocispec.Descriptor) error { // print the previous signature digest if prevDigest != "" { printTitle() fmt.Printf(" ├── %s\n", prevDigest) } prevDigest = sigManifestDesc.Digest return nil }) // print the last signature digest if prevDigest != "" { printTitle() fmt.Printf(" └── %s\n", prevDigest) } if err != nil { var errExceedMaxSignatures cmderr.ErrorExceedMaxSignatures if !errors.As(err, &errExceedMaxSignatures) { return err } fmt.Println("Warning:", errExceedMaxSignatures) } if !titlePrinted { fmt.Printf("%s has no associated signature\n", ref) } return nil } // listSignatures lists signatures associated with manifestDesc with number of // signatures limited by maxSig func listSignatures(ctx context.Context, sigRepo notationregistry.Repository, manifestDesc ocispec.Descriptor, maxSig int, fn func(sigManifest ocispec.Descriptor) error) error { numOfSignatureProcessed := 0 return sigRepo.ListSignatures(ctx, manifestDesc, func(signatureManifests []ocispec.Descriptor) error { for _, sigManifestDesc := range signatureManifests { if numOfSignatureProcessed >= maxSig { return cmderr.ErrorExceedMaxSignatures{MaxSignatures: maxSig} } numOfSignatureProcessed++ if err := fn(sigManifestDesc); err != nil { return err } } return nil }) } notation-1.2.0/cmd/notation/list_test.go000066400000000000000000000043031466375446100203240ustar00rootroot00000000000000// Copyright The Notary Project Authors. // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package main import ( "testing" ) func TestListCommand_SecretsFromArgs(t *testing.T) { opts := &listOpts{} cmd := listCommand(opts) expected := &listOpts{ reference: "ref", SecureFlagOpts: SecureFlagOpts{ Password: "password", InsecureRegistry: true, Username: "user", }, maxSignatures: 100, } if err := cmd.ParseFlags([]string{ "--password", expected.Password, expected.reference, "-u", expected.Username, "--insecure-registry"}); err != nil { t.Fatalf("Parse Flag failed: %v", err) } if err := cmd.Args(cmd, cmd.Flags().Args()); err != nil { t.Fatalf("Parse Args failed: %v", err) } if *opts != *expected { t.Fatalf("Expect list opts: %v, got: %v", expected, opts) } } func TestListCommand_SecretsFromEnv(t *testing.T) { t.Setenv(defaultUsernameEnv, "user") t.Setenv(defaultPasswordEnv, "password") opts := &listOpts{} expected := &listOpts{ reference: "ref", SecureFlagOpts: SecureFlagOpts{ Password: "password", Username: "user", }, maxSignatures: 100, } cmd := listCommand(opts) if err := cmd.ParseFlags([]string{ expected.reference}); err != nil { t.Fatalf("Parse Flag failed: %v", err) } if err := cmd.Args(cmd, cmd.Flags().Args()); err != nil { t.Fatalf("Parse Args failed: %v", err) } if *opts != *expected { t.Fatalf("Expect list opts: %v, got: %v", expected, opts) } } func TestListCommand_MissingArgs(t *testing.T) { cmd := listCommand(nil) if err := cmd.ParseFlags(nil); err != nil { t.Fatalf("Parse Flag failed: %v", err) } if err := cmd.Args(cmd, cmd.Flags().Args()); err == nil { t.Fatal("Parse Args expected error, but ok") } } notation-1.2.0/cmd/notation/login.go000066400000000000000000000140611466375446100174240ustar00rootroot00000000000000// Copyright The Notary Project Authors. // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package main import ( "bufio" "context" "errors" "fmt" "io" "os" "strings" "github.com/notaryproject/notation-go/log" "github.com/notaryproject/notation/internal/auth" "github.com/notaryproject/notation/internal/cmd" "github.com/spf13/cobra" "golang.org/x/term" "oras.land/oras-go/v2/registry/remote/credentials" ) const urlDocHowToAuthenticate = "https://notaryproject.dev/docs/how-to/registry-authentication/" type loginOpts struct { cmd.LoggingFlagOpts SecureFlagOpts passwordStdin bool server string } func loginCommand(opts *loginOpts) *cobra.Command { if opts == nil { opts = &loginOpts{} } command := &cobra.Command{ Use: "login [flags] ", Short: "Login to registry", Long: `Log in to an OCI registry Example - Login with provided username and password: notation login -u -p registry.example.com Example - Login using $NOTATION_USERNAME $NOTATION_PASSWORD variables: notation login registry.example.com`, Args: func(cmd *cobra.Command, args []string) error { if len(args) == 0 { return errors.New("no hostname specified") } opts.server = args[0] return nil }, PreRunE: func(cmd *cobra.Command, args []string) error { if err := readPassword(opts); err != nil { return err } return nil }, RunE: func(cmd *cobra.Command, args []string) error { return runLogin(cmd.Context(), opts) }, } opts.LoggingFlagOpts.ApplyFlags(command.Flags()) opts.SecureFlagOpts.ApplyFlags(command.Flags()) command.Flags().BoolVar(&opts.passwordStdin, "password-stdin", false, "take the password from stdin") return command } func runLogin(ctx context.Context, opts *loginOpts) error { // set log level ctx = opts.LoggingFlagOpts.InitializeLogger(ctx) // initialize serverAddress := opts.server // input username and password by prompt reader := bufio.NewReader(os.Stdin) var err error if opts.Password == "" { var isToken bool if opts.Username == "" { opts.Username, err = readUsernameFromPrompt(reader) if err != nil { return err } if opts.Username == "" { // the username is empty, the password is used as a token isToken = true } } opts.Password, err = readPasswordFromPrompt(reader, isToken) if err != nil { return err } if opts.Password == "" { if isToken { return errors.New("token required") } return errors.New("password required") } } cred := opts.Credential() credsStore, err := auth.NewCredentialsStore() if err != nil { return fmt.Errorf("failed to get credentials store: %v", err) } registry, err := getRegistryLoginClient(ctx, &opts.SecureFlagOpts, serverAddress) if err != nil { return fmt.Errorf("failed to get registry client: %v", err) } if err := credentials.Login(ctx, credsStore, registry, cred); err != nil { registryName := registry.Reference.Registry if !errors.Is(err, credentials.ErrPlaintextPutDisabled) { return fmt.Errorf("failed to log in to %s: %v", registryName, err) } // ErrPlaintextPutDisabled returned by Login() indicates that the // credential is validated but is not saved because there is no native // credentials store available credKeyName := credentials.ServerAddressFromRegistry(registryName) if savedCred, err := credsStore.Get(ctx, credKeyName); err != nil || savedCred != cred { if err != nil { // if we fail to get the saved credential, log a warning // but do not throw the GET error, as the error could be // confusing to users logger := log.GetLogger(ctx) logger.Warnf("Failed to get the existing credentials for %s: %v", registryName, err) } return fmt.Errorf("failed to log in to %s: the credential could not be saved because a credentials store is required to securely store the password. See %s", registryName, urlDocHowToAuthenticate) } // the credential already exists but is in plaintext, ignore the saving error fmt.Fprintf(os.Stderr, "Warning: The credentials store is not set up. It is recommended to configure the credentials store to securely store your credentials. See %s.\n", urlDocHowToAuthenticate) fmt.Println("Authenticated with existing credentials") } fmt.Println("Login Succeeded") return nil } func readPassword(opts *loginOpts) error { if opts.passwordStdin { password, err := readLine(os.Stdin) if err != nil { return err } opts.Password = password } return nil } func readLine(r io.Reader) (string, error) { passwordBytes, err := io.ReadAll(r) if err != nil { return "", err } password := strings.TrimSuffix(string(passwordBytes), "\n") password = strings.TrimSuffix(password, "\r") return password, nil } func readUsernameFromPrompt(reader *bufio.Reader) (string, error) { fmt.Print("Username: ") username, err := reader.ReadString('\n') if err != nil { return "", fmt.Errorf("error reading username: %w", err) } username = strings.TrimSpace(username) return username, nil } func readPasswordFromPrompt(reader *bufio.Reader, isToken bool) (string, error) { var passwordType string if isToken { passwordType = "token" fmt.Print("Token: ") } else { passwordType = "password" fmt.Print("Password: ") } if term.IsTerminal(int(os.Stdin.Fd())) { bytePassword, err := term.ReadPassword(int(os.Stdin.Fd())) if err != nil { return "", fmt.Errorf("error reading %s: %w", passwordType, err) } fmt.Println() return string(bytePassword), nil } else { password, err := readLine(reader) if err != nil { return "", fmt.Errorf("error reading %s: %w", passwordType, err) } fmt.Println() return password, nil } } notation-1.2.0/cmd/notation/login_test.go000066400000000000000000000052011466375446100204570ustar00rootroot00000000000000// Copyright The Notary Project Authors. // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package main import ( "os" "testing" ) func TestLoginCommand_PasswordFromArgs(t *testing.T) { t.Setenv(defaultUsernameEnv, "user") opts := &loginOpts{} cmd := loginCommand(opts) expected := &loginOpts{ SecureFlagOpts: SecureFlagOpts{ Username: "user", Password: "password", }, server: "server", } if err := cmd.ParseFlags([]string{ expected.server, "-u", expected.Username, "-p", expected.Password, }); err != nil { t.Fatalf("Parse Flag failed: %v", err) } if err := cmd.Args(cmd, cmd.Flags().Args()); err != nil { t.Fatalf("Parse args failed: %v", err) } if err := cmd.PreRunE(cmd, cmd.Flags().Args()); err != nil { t.Fatalf("Get password failed: %v", err) } if *opts != *expected { t.Fatalf("Expect login opts: %v, got: %v", expected, opts) } } func TestLogin_PasswordFromStdin(t *testing.T) { t.Setenv(defaultUsernameEnv, "user") opts := &loginOpts{} cmd := loginCommand(opts) expected := &loginOpts{ passwordStdin: true, SecureFlagOpts: SecureFlagOpts{ Username: "user", Password: "password", }, server: "server", } if err := cmd.ParseFlags([]string{ expected.server, "--password-stdin", "-u", expected.Username, "-p", expected.Password, }); err != nil { t.Fatalf("Parse Flag failed: %v", err) } if err := cmd.Args(cmd, cmd.Flags().Args()); err != nil { t.Fatalf("Parse Args failed: %v", err) } r, w, err := os.Pipe() w.Write([]byte("password")) w.Close() oldStdin := os.Stdin defer func() { os.Stdin = oldStdin }() os.Stdin = r if err != nil { t.Fatalf("Create test pipe for login cmd failed: %v", err) } if err := cmd.PreRunE(cmd, cmd.Flags().Args()); err != nil { t.Fatalf("Read password from stdin failed: %v", err) } if *opts != *expected { t.Fatalf("Expect login opts: %+v, got: %+v", expected, opts) } } func TestLoginCommand_MissingArgs(t *testing.T) { cmd := loginCommand(nil) if err := cmd.ParseFlags(nil); err != nil { t.Fatalf("Parse Flag failed: %v", err) } if err := cmd.Args(cmd, cmd.Flags().Args()); err == nil { t.Fatal("Parse Args expected error, but ok") } } notation-1.2.0/cmd/notation/logout.go000066400000000000000000000035631466375446100176320ustar00rootroot00000000000000// Copyright The Notary Project Authors. // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package main import ( "context" "errors" "fmt" "github.com/notaryproject/notation/internal/auth" "github.com/notaryproject/notation/internal/cmd" "github.com/spf13/cobra" "oras.land/oras-go/v2/registry/remote/credentials" ) type logoutOpts struct { cmd.LoggingFlagOpts server string } func logoutCommand(opts *logoutOpts) *cobra.Command { if opts == nil { opts = &logoutOpts{} } command := &cobra.Command{ Use: "logout [flags] ", Short: "Log out from the logged in registries", Args: func(cmd *cobra.Command, args []string) error { if len(args) == 0 { return errors.New("no hostname specified") } opts.server = args[0] return nil }, RunE: func(cmd *cobra.Command, args []string) error { return runLogout(cmd.Context(), opts) }, } opts.LoggingFlagOpts.ApplyFlags(command.Flags()) return command } func runLogout(ctx context.Context, opts *logoutOpts) error { // set log level ctx = opts.LoggingFlagOpts.InitializeLogger(ctx) credsStore, err := auth.NewCredentialsStore() if err != nil { return fmt.Errorf("failed to get credentials store: %v", err) } if err := credentials.Logout(ctx, credsStore, opts.server); err != nil { return fmt.Errorf("failed to log out of %s: %v", opts.server, err) } fmt.Println("Logout Succeeded") return nil } notation-1.2.0/cmd/notation/logout_test.go000066400000000000000000000025341466375446100206660ustar00rootroot00000000000000// Copyright The Notary Project Authors. // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package main import "testing" func TestLogoutCommand_BasicArgs(t *testing.T) { opts := &logoutOpts{} cmd := logoutCommand(opts) expected := &logoutOpts{ server: "server", } if err := cmd.ParseFlags([]string{ expected.server, }); err != nil { t.Fatalf("Parse Flag failed: %v", err) } if err := cmd.Args(cmd, cmd.Flags().Args()); err != nil { t.Fatalf("Parse Args failed: %v", err) } if *opts != *expected { t.Fatalf("Expect logout opts: %v, got: %v", expected, opts) } } func TestLogOutCommand_MissingArgs(t *testing.T) { cmd := logoutCommand(nil) if err := cmd.ParseFlags([]string{}); err != nil { t.Fatalf("Parse Flag failed: %v", err) } if err := cmd.Args(cmd, cmd.Flags().Args()); err == nil { t.Fatal("Parse Args expected error, but ok") } } notation-1.2.0/cmd/notation/main.go000066400000000000000000000035461466375446100172460ustar00rootroot00000000000000// Copyright The Notary Project Authors. // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package main import ( "os" "github.com/notaryproject/notation-go/dir" "github.com/notaryproject/notation/cmd/notation/cert" "github.com/notaryproject/notation/cmd/notation/plugin" "github.com/notaryproject/notation/cmd/notation/policy" "github.com/spf13/cobra" ) func main() { cmd := &cobra.Command{ Use: "notation", Short: "Notation - a tool to sign and verify artifacts", SilenceUsage: true, PersistentPreRun: func(cmd *cobra.Command, args []string) { // unset registry credentials after read the value from environment // to avoid leaking credentials os.Unsetenv(defaultUsernameEnv) os.Unsetenv(defaultPasswordEnv) // update Notation config directory if notationConfig := os.Getenv("NOTATION_CONFIG"); notationConfig != "" { dir.UserConfigDir = notationConfig } // update Notation Libexec directory (for plugins) if notationLibexec := os.Getenv("NOTATION_LIBEXEC"); notationLibexec != "" { dir.UserLibexecDir = notationLibexec } }, } cmd.AddCommand( signCommand(nil), verifyCommand(nil), listCommand(nil), cert.Cmd(), policy.Cmd(), keyCommand(), plugin.Cmd(), loginCommand(nil), logoutCommand(nil), versionCommand(), inspectCommand(nil), ) if err := cmd.Execute(); err != nil { os.Exit(1) } } notation-1.2.0/cmd/notation/main_test.go000066400000000000000000000022711466375446100202770ustar00rootroot00000000000000// Copyright The Notary Project Authors. // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package main import ( "os" "testing" ) func Test_UnsetEnvCredential(t *testing.T) { const notationUsername = "NOTATION_USERNAME" const notationPassword = "NOTATION_PASSWORD" // Set environment variables for testing os.Setenv(notationUsername, "testuser") os.Setenv(notationPassword, "testpassword") os.Args = []string{"notation", "version"} main() // check credentials environment variables are unset if os.Getenv(notationUsername) != "" { t.Errorf("expected %s to be unset", notationUsername) } if os.Getenv(notationPassword) != "" { t.Errorf("expected %s to be unset", notationPassword) } } notation-1.2.0/cmd/notation/manifest.go000066400000000000000000000140371466375446100201250ustar00rootroot00000000000000// Copyright The Notary Project Authors. // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package main import ( "context" "errors" "fmt" "os" "strings" "unicode" "github.com/notaryproject/notation-go/log" notationregistry "github.com/notaryproject/notation-go/registry" notationerrors "github.com/notaryproject/notation/cmd/notation/internal/errors" "github.com/opencontainers/go-digest" ocispec "github.com/opencontainers/image-spec/specs-go/v1" "oras.land/oras-go/v2/registry" ) func resolveReferenceWithWarning(ctx context.Context, inputType inputType, reference string, sigRepo notationregistry.Repository, operation string) (ocispec.Descriptor, string, error) { return resolveReference(ctx, inputType, reference, sigRepo, func(ref string, manifestDesc ocispec.Descriptor) { fmt.Fprintf(os.Stderr, "Warning: Always %s the artifact using digest(@sha256:...) rather than a tag(:%s) because resolved digest may not point to the same signed artifact, as tags are mutable.\n", operation, ref) }) } // resolveReference resolves user input reference based on user input type. // Returns the resolved manifest descriptor and resolvedRef in digest func resolveReference(ctx context.Context, inputType inputType, reference string, sigRepo notationregistry.Repository, fn func(string, ocispec.Descriptor)) (ocispec.Descriptor, string, error) { // sanity check if reference == "" { return ocispec.Descriptor{}, "", errors.New("missing user input reference") } var tagOrDigestRef string var resolvedRef string switch inputType { case inputTypeRegistry: ref, err := registry.ParseReference(reference) if err != nil { return ocispec.Descriptor{}, "", fmt.Errorf("%q: %w. Expecting /: or /@", reference, err) } if ref.Reference == "" { return ocispec.Descriptor{}, "", fmt.Errorf("%q: invalid reference: no tag or digest. Expecting /: or /@", reference) } tagOrDigestRef = ref.Reference resolvedRef = ref.Registry + "/" + ref.Repository case inputTypeOCILayout: layoutPath, layoutReference, err := parseOCILayoutReference(reference) if err != nil { return ocispec.Descriptor{}, "", fmt.Errorf("failed to resolve user input reference: %w", err) } layoutPathInfo, err := os.Stat(layoutPath) if err != nil { return ocispec.Descriptor{}, "", fmt.Errorf("failed to resolve user input reference: %w", err) } if !layoutPathInfo.IsDir() { return ocispec.Descriptor{}, "", errors.New("failed to resolve user input reference: input path is not a dir") } tagOrDigestRef = layoutReference resolvedRef = layoutPath default: return ocispec.Descriptor{}, "", fmt.Errorf("unsupported user inputType: %d", inputType) } manifestDesc, err := getManifestDescriptor(ctx, tagOrDigestRef, sigRepo) if err != nil { return ocispec.Descriptor{}, "", fmt.Errorf("failed to get manifest descriptor: %w", err) } resolvedRef = resolvedRef + "@" + manifestDesc.Digest.String() if _, err := digest.Parse(tagOrDigestRef); err == nil { // tagOrDigestRef is a digest reference if tagOrDigestRef != manifestDesc.Digest.String() { // tagOrDigestRef does not match the resolved digest return ocispec.Descriptor{}, "", fmt.Errorf("user input digest %s does not match the resolved digest %s", tagOrDigestRef, manifestDesc.Digest.String()) } return manifestDesc, resolvedRef, nil } // tagOrDigestRef is a tag reference if fn != nil { fn(tagOrDigestRef, manifestDesc) } return manifestDesc, resolvedRef, nil } // resolveArtifactDigestReference creates reference in Verification given user input // trust policy scope func resolveArtifactDigestReference(reference, policyScope string) string { if policyScope != "" { if _, digest, ok := strings.Cut(reference, "@"); ok { return policyScope + "@" + digest } } return reference } // parseOCILayoutReference parses the raw in format of [:|@]. // Returns the path to the OCI layout and the reference (tag or digest). func parseOCILayoutReference(raw string) (string, string, error) { var path string var ref string if idx := strings.LastIndex(raw, "@"); idx != -1 { // `digest` found path, ref = raw[:idx], raw[idx+1:] } else { // find `tag` idx := strings.LastIndex(raw, ":") if idx == -1 || (idx == 1 && len(raw) > 2 && unicode.IsLetter(rune(raw[0])) && raw[2] == '\\') { return "", "", notationerrors.ErrorOCILayoutMissingReference{Msg: fmt.Sprintf("%q: invalid reference: missing tag or digest. Expecting : or @", raw)} } else { path, ref = raw[:idx], raw[idx+1:] } } if path == "" { return "", "", fmt.Errorf("%q: invalid reference: missing oci-layout file path. Expecting : or @", raw) } if ref == "" { return "", "", notationerrors.ErrorOCILayoutMissingReference{Msg: fmt.Sprintf("%q: invalid reference: missing tag or digest. Expecting : or @", raw)} } return path, ref, nil } // getManifestDescriptor returns target artifact's manifest descriptor given // reference (digest or tag) and Repository. func getManifestDescriptor(ctx context.Context, reference string, sigRepo notationregistry.Repository) (ocispec.Descriptor, error) { logger := log.GetLogger(ctx) if reference == "" { return ocispec.Descriptor{}, errors.New("reference cannot be empty") } manifestDesc, err := sigRepo.Resolve(ctx, reference) if err != nil { return ocispec.Descriptor{}, err } logger.Infof("Reference %s resolved to manifest descriptor: %+v", reference, manifestDesc) return manifestDesc, nil } notation-1.2.0/cmd/notation/plugin/000077500000000000000000000000001466375446100172615ustar00rootroot00000000000000notation-1.2.0/cmd/notation/plugin/cmd.go000066400000000000000000000015301466375446100203520ustar00rootroot00000000000000// Copyright The Notary Project Authors. // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package plugin import "github.com/spf13/cobra" func Cmd() *cobra.Command { command := &cobra.Command{ Use: "plugin", Short: "Manage plugins", } command.AddCommand( listCommand(), installCommand(nil), uninstallCommand(nil), ) return command } notation-1.2.0/cmd/notation/plugin/install.go000066400000000000000000000277731466375446100212760ustar00rootroot00000000000000// Copyright The Notary Project Authors. // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package plugin import ( "archive/tar" "archive/zip" "compress/gzip" "context" "errors" "fmt" "io" "io/fs" "net/url" "os" "path/filepath" "runtime" "strings" "github.com/notaryproject/notation-go/dir" "github.com/notaryproject/notation-go/log" "github.com/notaryproject/notation-go/plugin" notationplugin "github.com/notaryproject/notation/cmd/notation/internal/plugin" "github.com/notaryproject/notation/internal/cmd" "github.com/notaryproject/notation/internal/osutil" "github.com/spf13/cobra" ) const ( notationPluginTmpDir = "notation-plugin" notationPluginDownloadTmpFile = "notation-plugin-download" ) type pluginInstallOpts struct { cmd.LoggingFlagOpts pluginSourceType notationplugin.PluginSourceType pluginSource string inputChecksum string isFile bool isURL bool force bool } func installCommand(opts *pluginInstallOpts) *cobra.Command { if opts == nil { opts = &pluginInstallOpts{} } command := &cobra.Command{ Use: "install [flags] <--file|--url> ", Aliases: []string{"add"}, Short: "Install a plugin", Long: `Install a plugin Example - Install plugin from file system: notation plugin install --file wabbit-plugin-v1.0.zip Example - Install plugin from file system with user input SHA256 checksum: notation plugin install --file wabbit-plugin-v1.0.zip --sha256sum 113062a462674a0e35cb5cad75a0bb2ea16e9537025531c0fd705018fcdbc17e Example - Install plugin from file system regardless if it's already installed: notation plugin install --file wabbit-plugin-v1.0.zip --force Example - Install plugin from file system with .tar.gz: notation plugin install --file wabbit-plugin-v1.0.tar.gz Example - Install plugin from file system with a single plugin executable file: notation plugin install --file notation-wabbit-plugin Example - Install plugin from URL, SHA256 checksum is required: notation plugin install --url https://wabbit-networks.com/intaller/linux/amd64/wabbit-plugin-v1.0.tar.gz --sha256sum f8a75d9234db90069d9eb5660e5374820edf36d710bd063f4ef81e7063d3810b `, Args: func(cmd *cobra.Command, args []string) error { if len(args) == 0 { switch { case opts.isFile: return errors.New("missing plugin file path") case opts.isURL: return errors.New("missing plugin URL") } return errors.New("missing plugin source location") } if len(args) > 1 { return fmt.Errorf("can only install one plugin at a time, but got %v", args) } opts.pluginSource = args[0] return nil }, RunE: func(cmd *cobra.Command, args []string) error { switch { case opts.isFile: opts.pluginSourceType = notationplugin.PluginSourceTypeFile case opts.isURL: opts.pluginSourceType = notationplugin.PluginSourceTypeURL } return install(cmd, opts) }, } opts.LoggingFlagOpts.ApplyFlags(command.Flags()) command.Flags().BoolVar(&opts.isFile, "file", false, "install plugin from a file on file system") command.Flags().BoolVar(&opts.isURL, "url", false, fmt.Sprintf("install plugin from an HTTPS URL. The plugin download timeout is %s", notationplugin.DownloadPluginFromURLTimeout)) command.Flags().StringVar(&opts.inputChecksum, "sha256sum", "", "must match SHA256 of the plugin source, required when \"--url\" flag is set") command.Flags().BoolVar(&opts.force, "force", false, "force the installation of the plugin") command.MarkFlagsMutuallyExclusive("file", "url") command.MarkFlagsOneRequired("file", "url") return command } func install(command *cobra.Command, opts *pluginInstallOpts) error { // set log level ctx := opts.LoggingFlagOpts.InitializeLogger(command.Context()) // core process switch opts.pluginSourceType { case notationplugin.PluginSourceTypeFile: if err := installPlugin(ctx, opts.pluginSource, opts.inputChecksum, opts.force); err != nil { return fmt.Errorf("plugin installation failed: %w", err) } return nil case notationplugin.PluginSourceTypeURL: if opts.inputChecksum == "" { return errors.New("installing from URL requires non-empty SHA256 checksum of the plugin source") } pluginURL, err := url.Parse(opts.pluginSource) if err != nil { return fmt.Errorf("failed to parse plugin download URL %s with error: %w", pluginURL, err) } if pluginURL.Scheme != "https" { return fmt.Errorf("failed to download plugin from URL: only the HTTPS scheme is supported, but got %s", pluginURL.Scheme) } tmpFile, err := os.CreateTemp("", notationPluginDownloadTmpFile) if err != nil { return fmt.Errorf("failed to create temporary file required for downloading plugin: %w", err) } defer os.Remove(tmpFile.Name()) defer tmpFile.Close() fmt.Printf("Downloading plugin from %s\n", opts.pluginSource) err = notationplugin.DownloadPluginFromURL(ctx, opts.pluginSource, tmpFile) if err != nil { return fmt.Errorf("failed to download plugin from URL %s with error: %w", opts.pluginSource, err) } fmt.Println("Download completed") if err := installPlugin(ctx, tmpFile.Name(), opts.inputChecksum, opts.force); err != nil { return fmt.Errorf("plugin installation failed: %w", err) } return nil default: return errors.New("plugin installation failed: unknown plugin source type") } } // installPlugin installs the plugin given plugin source path func installPlugin(ctx context.Context, inputPath string, inputChecksum string, force bool) error { // sanity check inputFileInfo, err := os.Stat(inputPath) if err != nil { return err } if !inputFileInfo.Mode().IsRegular() { return fmt.Errorf("%s is not a valid file", inputPath) } // checksum check if inputChecksum != "" { if err := osutil.ValidateSHA256Sum(inputPath, inputChecksum); err != nil { return err } } // install the plugin based on file type fileType, err := osutil.DetectFileType(inputPath) if err != nil { return err } switch fileType { case notationplugin.MediaTypeZip: rc, err := zip.OpenReader(inputPath) if err != nil { return err } defer rc.Close() // check for '..' in file name to avoid zip slip vulnerability for _, f := range rc.File { if strings.Contains(f.Name, "..") { return fmt.Errorf("file name in zip cannot contain '..', but found %q", f.Name) } } return installPluginFromFS(ctx, rc, force) case notationplugin.MediaTypeGzip: // when file is gzip, required to be tar return installPluginFromTarGz(ctx, inputPath, force) default: // input file is not in zip or gzip, try install directly if inputFileInfo.Size() >= osutil.MaxFileBytes { return fmt.Errorf("file size reached the %d MiB size limit", osutil.MaxFileBytes/1024/1024) } installOpts := plugin.CLIInstallOptions{ PluginPath: inputPath, Overwrite: force, } return installPluginWithOptions(ctx, installOpts) } } // installPluginFromFS extracts, validates and installs the plugin files // from a fs.FS // // Note: zip.ReadCloser implments fs.FS func installPluginFromFS(ctx context.Context, pluginFS fs.FS, force bool) error { // set up logger logger := log.GetLogger(ctx) root := "." // extracting all regular files from root into tmpDir tmpDir, err := os.MkdirTemp("", notationPluginTmpDir) if err != nil { return fmt.Errorf("failed to create temporary directory: %w", err) } defer os.RemoveAll(tmpDir) var pluginFileSize int64 if err := fs.WalkDir(pluginFS, root, func(path string, d fs.DirEntry, err error) error { if err != nil { return err } fName := d.Name() if d.IsDir() && fName != root { // skip any dir in the fs except root return fs.SkipDir } info, err := d.Info() if err != nil { return err } // only accept regular files if !info.Mode().IsRegular() { return nil } // check for plugin file size to avoid zip bomb vulnerability pluginFileSize += info.Size() if pluginFileSize >= osutil.MaxFileBytes { return fmt.Errorf("total file size reached the %d MiB size limit", osutil.MaxFileBytes/1024/1024) } logger.Debugf("Extracting file %s...", fName) rc, err := pluginFS.Open(path) if err != nil { return err } defer rc.Close() tmpFilePath := filepath.Join(tmpDir, fName) return osutil.CopyFromReaderToDir(rc, tmpFilePath, info.Mode()) }); err != nil { return err } // install core process installOpts := plugin.CLIInstallOptions{ PluginPath: tmpDir, Overwrite: force, } return installPluginWithOptions(ctx, installOpts) } // installPluginFromTarGz extracts and untar a plugin tar.gz file, validates and // installs the plugin func installPluginFromTarGz(ctx context.Context, tarGzPath string, force bool) error { logger := log.GetLogger(ctx) rc, err := os.Open(tarGzPath) if err != nil { return err } defer rc.Close() decompressedStream, err := gzip.NewReader(rc) if err != nil { return err } defer decompressedStream.Close() tarReader := tar.NewReader(decompressedStream) // extracting all regular files into tmpDir tmpDir, err := os.MkdirTemp("", notationPluginTmpDir) if err != nil { return fmt.Errorf("failed to create temporary directory: %w", err) } defer os.RemoveAll(tmpDir) var pluginFileSize int64 for { header, err := tarReader.Next() if err != nil { if err == io.EOF { break } return err } // check for '..' in file name to avoid zip slip vulnerability if strings.Contains(header.Name, "..") { return fmt.Errorf("file name in tar.gz cannot contain '..', but found %q", header.Name) } // only accept regular files if !header.FileInfo().Mode().IsRegular() { continue } // check for plugin file size to avoid zip bomb vulnerability pluginFileSize += header.FileInfo().Size() if pluginFileSize >= osutil.MaxFileBytes { return fmt.Errorf("total file size reached the %d MiB size limit", osutil.MaxFileBytes/1024/1024) } fName := filepath.Base(header.Name) logger.Debugf("Extracting file %s...", fName) tmpFilePath := filepath.Join(tmpDir, fName) if err := osutil.CopyFromReaderToDir(tarReader, tmpFilePath, header.FileInfo().Mode()); err != nil { return err } } // install core process installOpts := plugin.CLIInstallOptions{ PluginPath: tmpDir, Overwrite: force, } return installPluginWithOptions(ctx, installOpts) } // installPluginWithOptions installs plugin with CLIInstallOptions func installPluginWithOptions(ctx context.Context, opts plugin.CLIInstallOptions) error { mgr := plugin.NewCLIManager(dir.PluginFS()) existingPluginMetadata, newPluginMetadata, err := mgr.Install(ctx, opts) if err != nil { var errPluginDowngrade plugin.PluginDowngradeError if errors.As(err, &errPluginDowngrade) { return fmt.Errorf("%w.\nIt is not recommended to install an older version. To force the installation, use the \"--force\" option", errPluginDowngrade) } var errExeFile *plugin.PluginExecutableFileError if errors.As(err, &errExeFile) { return fmt.Errorf("%w.\nPlease ensure that the plugin executable file is compatible with %s/%s and has appropriate permissions.", err, runtime.GOOS, runtime.GOARCH) } var errMalformedPlugin *plugin.PluginMalformedError if errors.As(err, &errMalformedPlugin) { return fmt.Errorf("%w.\nPlease ensure that the plugin executable file is intact and compatible with %s/%s. Contact the plugin publisher for further assistance.", errMalformedPlugin, runtime.GOOS, runtime.GOARCH) } return err } if existingPluginMetadata != nil { fmt.Printf("Successfully updated plugin %s from version %s to %s\n", newPluginMetadata.Name, existingPluginMetadata.Version, newPluginMetadata.Version) } else { fmt.Printf("Successfully installed plugin %s, version %s\n", newPluginMetadata.Name, newPluginMetadata.Version) } return nil } notation-1.2.0/cmd/notation/plugin/list.go000066400000000000000000000042731466375446100205710ustar00rootroot00000000000000// Copyright The Notary Project Authors. // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package plugin import ( "errors" "fmt" "os" "text/tabwriter" "github.com/notaryproject/notation-go/dir" "github.com/notaryproject/notation-go/plugin" "github.com/notaryproject/notation-go/plugin/proto" "github.com/spf13/cobra" ) func listCommand() *cobra.Command { return &cobra.Command{ Use: "list [flags]", Aliases: []string{"ls"}, Short: "List installed plugins", Long: `List installed plugins Example - List installed Notation plugins: notation plugin ls `, RunE: func(cmd *cobra.Command, args []string) error { return listPlugins(cmd) }, } } func listPlugins(command *cobra.Command) error { mgr := plugin.NewCLIManager(dir.PluginFS()) pluginNames, err := mgr.List(command.Context()) if err != nil { var errPluginDirWalk plugin.PluginDirectoryWalkError if errors.As(err, &errPluginDirWalk) { pluginDir, _ := dir.PluginFS().SysPath("") return fmt.Errorf("%w.\nPlease ensure that the current user has permission to access the plugin directory: %s", errPluginDirWalk, pluginDir) } return err } tw := tabwriter.NewWriter(os.Stdout, 0, 0, 3, ' ', 0) fmt.Fprintln(tw, "NAME\tDESCRIPTION\tVERSION\tCAPABILITIES\tERROR\t") var pl plugin.Plugin var resp *proto.GetMetadataResponse for _, n := range pluginNames { pl, err = mgr.Get(command.Context(), n) metaData := &proto.GetMetadataResponse{} if err == nil { resp, err = pl.GetMetadata(command.Context(), &proto.GetMetadataRequest{}) if err == nil { metaData = resp } } fmt.Fprintf(tw, "%s\t%s\t%s\t%v\t%v\t\n", n, metaData.Description, metaData.Version, metaData.Capabilities, err) } return tw.Flush() } notation-1.2.0/cmd/notation/plugin/uninstall.go000066400000000000000000000063431466375446100216270ustar00rootroot00000000000000// Copyright The Notary Project Authors. // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package plugin import ( "context" "errors" "fmt" "os" "github.com/notaryproject/notation-go/dir" "github.com/notaryproject/notation-go/plugin" "github.com/notaryproject/notation/cmd/notation/internal/cmdutil" "github.com/notaryproject/notation/internal/cmd" "github.com/spf13/cobra" ) type pluginUninstallOpts struct { cmd.LoggingFlagOpts pluginName string confirmed bool } func uninstallCommand(opts *pluginUninstallOpts) *cobra.Command { if opts == nil { opts = &pluginUninstallOpts{} } command := &cobra.Command{ Use: "uninstall [flags] ", Aliases: []string{"remove", "rm"}, Short: "Uninstall a plugin", Long: `Uninstall a plugin Example - Uninstall plugin: notation plugin uninstall wabbit-plugin `, Args: func(cmd *cobra.Command, args []string) error { if len(args) == 0 { return errors.New("plugin name is required") } if len(args) > 1 { return errors.New("only one plugin can be removed at a time") } opts.pluginName = args[0] return nil }, RunE: func(cmd *cobra.Command, args []string) error { return uninstallPlugin(cmd, opts) }, } opts.LoggingFlagOpts.ApplyFlags(command.Flags()) command.Flags().BoolVarP(&opts.confirmed, "yes", "y", false, "do not prompt for confirmation") return command } func uninstallPlugin(command *cobra.Command, opts *pluginUninstallOpts) error { // set logger ctx := opts.LoggingFlagOpts.InitializeLogger(command.Context()) pluginName := opts.pluginName exist, err := checkPluginExistence(ctx, pluginName) if err != nil { return fmt.Errorf("failed to check plugin existence: %w", err) } if !exist { return fmt.Errorf("unable to find plugin %s.\nTo view a list of installed plugins, use `notation plugin list`", pluginName) } // core process prompt := fmt.Sprintf("Are you sure you want to uninstall plugin %q?", pluginName) confirmed, err := cmdutil.AskForConfirmation(os.Stdin, prompt, opts.confirmed) if err != nil { return fmt.Errorf("failed when asking for confirmation: %w", err) } if !confirmed { return nil } mgr := plugin.NewCLIManager(dir.PluginFS()) if err := mgr.Uninstall(ctx, pluginName); err != nil { return fmt.Errorf("failed to uninstall plugin %s: %w", pluginName, err) } fmt.Printf("Successfully uninstalled plugin %s\n", pluginName) return nil } // checkPluginExistence returns true if plugin exists in the system func checkPluginExistence(ctx context.Context, pluginName string) (bool, error) { mgr := plugin.NewCLIManager(dir.PluginFS()) _, err := mgr.Get(ctx, pluginName) if err != nil { if errors.Is(err, os.ErrNotExist) { // plugin does not exist return false, nil } return false, err } return true, nil } notation-1.2.0/cmd/notation/policy/000077500000000000000000000000001466375446100172625ustar00rootroot00000000000000notation-1.2.0/cmd/notation/policy/cmd.go000066400000000000000000000016321466375446100203560ustar00rootroot00000000000000// Copyright The Notary Project Authors. // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package policy import "github.com/spf13/cobra" func Cmd() *cobra.Command { command := &cobra.Command{ Use: "policy [command]", Short: "Manage trust policy configuration", Long: "Manage trust policy configuration for signature verification.", } command.AddCommand( showCmd(), importCmd(), ) return command } notation-1.2.0/cmd/notation/policy/import.go000066400000000000000000000063101466375446100211230ustar00rootroot00000000000000// Copyright The Notary Project Authors. // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package policy import ( "encoding/json" "fmt" "os" "github.com/notaryproject/notation-go/dir" "github.com/notaryproject/notation-go/verifier/trustpolicy" "github.com/notaryproject/notation/cmd/notation/internal/cmdutil" "github.com/notaryproject/notation/internal/osutil" "github.com/spf13/cobra" ) type importOpts struct { filePath string force bool } func importCmd() *cobra.Command { var opts importOpts command := &cobra.Command{ Use: "import [flags] ", Short: "Import trust policy configuration from a JSON file", Long: `Import trust policy configuration from a JSON file. ** This command is in preview and under development. ** Example - Import trust policy configuration from a file: notation policy import my_policy.json `, Args: func(cmd *cobra.Command, args []string) error { if len(args) != 1 { return fmt.Errorf("requires 1 argument but received %d.\nUsage: notation policy import \nPlease specify a trust policy file location as the argument", len(args)) } return nil }, RunE: func(cmd *cobra.Command, args []string) error { opts.filePath = args[0] return runImport(cmd, opts) }, } command.Flags().BoolVar(&opts.force, "force", false, "override the existing trust policy configuration, never prompt") return command } func runImport(command *cobra.Command, opts importOpts) error { // read configuration policyJSON, err := os.ReadFile(opts.filePath) if err != nil { return fmt.Errorf("failed to read trust policy file: %w", err) } // parse and validate var doc trustpolicy.Document if err = json.Unmarshal(policyJSON, &doc); err != nil { return fmt.Errorf("failed to parse trust policy configuration: %w", err) } if err = doc.Validate(); err != nil { return fmt.Errorf("failed to validate trust policy: %w", err) } // optional confirmation if !opts.force { if _, err := trustpolicy.LoadDocument(); err == nil { confirmed, err := cmdutil.AskForConfirmation(os.Stdin, "The trust policy file already exists, do you want to overwrite it?", opts.force) if err != nil { return err } if !confirmed { return nil } } } else { fmt.Fprintln(os.Stderr, "Warning: existing trust policy configuration file will be overwritten") } // write policyPath, err := dir.ConfigFS().SysPath(dir.PathTrustPolicy) if err != nil { return fmt.Errorf("failed to obtain path of trust policy file: %w", err) } if err = osutil.WriteFile(policyPath, policyJSON); err != nil { return fmt.Errorf("failed to write trust policy file: %w", err) } _, err = fmt.Fprintln(os.Stdout, "Trust policy configuration imported successfully.") return err } notation-1.2.0/cmd/notation/policy/show.go000066400000000000000000000046541466375446100206020ustar00rootroot00000000000000// Copyright The Notary Project Authors. // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package policy import ( "encoding/json" "errors" "fmt" "io/fs" "os" "github.com/notaryproject/notation-go/dir" "github.com/notaryproject/notation-go/verifier/trustpolicy" "github.com/spf13/cobra" ) type showOpts struct { } func showCmd() *cobra.Command { var opts showOpts command := &cobra.Command{ Use: "show [flags]", Short: "Show trust policy configuration", Long: `Show trust policy configuration. ** This command is in preview and under development. ** Example - Show current trust policy configuration: notation policy show Example - Save current trust policy configuration to a file: notation policy show > my_policy.json `, Args: cobra.ExactArgs(0), RunE: func(cmd *cobra.Command, args []string) error { return runShow(cmd, opts) }, } return command } func runShow(command *cobra.Command, opts showOpts) error { // get policy file path policyPath, err := dir.ConfigFS().SysPath(dir.PathTrustPolicy) if err != nil { return fmt.Errorf("failed to obtain path of trust policy file: %w", err) } // core process policyJSON, err := os.ReadFile(policyPath) if err != nil { if errors.Is(err, fs.ErrNotExist) { return fmt.Errorf("failed to show trust policy as the trust policy file does not exist.\nYou can import one using `notation policy import `") } return fmt.Errorf("failed to show trust policy: %w", err) } var doc trustpolicy.Document if err = json.Unmarshal(policyJSON, &doc); err == nil { err = doc.Validate() } if err != nil { fmt.Fprintf(os.Stderr, "Error: %s\n", err.Error()) fmt.Fprintf(os.Stderr, "Existing trust policy configuration is invalid, you may update or create a new one via `notation policy import `\n") // not returning to show the invalid policy configuration } // show policy content _, err = os.Stdout.Write(policyJSON) return err } notation-1.2.0/cmd/notation/registry.go000066400000000000000000000131461466375446100201670ustar00rootroot00000000000000// Copyright The Notary Project Authors. // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package main import ( "context" "errors" "fmt" "net" "github.com/notaryproject/notation-go/log" notationregistry "github.com/notaryproject/notation-go/registry" notationauth "github.com/notaryproject/notation/internal/auth" "github.com/notaryproject/notation/internal/httputil" "github.com/notaryproject/notation/pkg/configutil" "oras.land/oras-go/v2/registry" "oras.land/oras-go/v2/registry/remote" "oras.land/oras-go/v2/registry/remote/auth" "oras.land/oras-go/v2/registry/remote/credentials" ) // inputType denotes the user input type type inputType int const ( inputTypeRegistry inputType = 1 + iota // inputType remote registry inputTypeOCILayout // inputType oci-layout ) // getRepository returns a notationregistry.Repository given user input // type and user input reference func getRepository(ctx context.Context, inputType inputType, reference string, opts *SecureFlagOpts, forceReferrersTag bool) (notationregistry.Repository, error) { switch inputType { case inputTypeRegistry: return getRemoteRepository(ctx, opts, reference, forceReferrersTag) case inputTypeOCILayout: layoutPath, _, err := parseOCILayoutReference(reference) if err != nil { return nil, err } return notationregistry.NewOCIRepository(layoutPath, notationregistry.RepositoryOptions{}) default: return nil, errors.New("unsupported input type") } } // getRemoteRepository returns a registry.Repository. // When forceReferrersTag is true, Notation will always generate an image index // according to the Referrers tag schema to store signature. // // When forceReferrersTag is false, Notation will first try to store the // signature as a referrer according to the Referrers API. If the Referrers API // is not supported, fallback to use the referrers tag schema. // This flag is always FALSE when verify/list/inspect signatures. // // References: // https://github.com/opencontainers/distribution-spec/blob/v1.1.0/spec.md#listing-referrers // https://github.com/opencontainers/distribution-spec/blob/v1.1.0/spec.md#referrers-tag-schema func getRemoteRepository(ctx context.Context, opts *SecureFlagOpts, reference string, forceReferrersTag bool) (notationregistry.Repository, error) { logger := log.GetLogger(ctx) ref, err := registry.ParseReference(reference) if err != nil { return nil, fmt.Errorf("%q: %w. Expecting /: or /@", reference, err) } if ref.Reference == "" { return nil, fmt.Errorf("%q: invalid reference: no tag or digest. Expecting /: or /@", reference) } // generate notation repository remoteRepo, err := getRepositoryClient(ctx, opts, ref) if err != nil { return nil, err } if forceReferrersTag { logger.Info("The referrers tag schema is always attempted") if err := remoteRepo.SetReferrersCapability(false); err != nil { return nil, err } } else { logger.Info("Allowed to access the referrers API, fallback if not supported") } return notationregistry.NewRepository(remoteRepo), nil } func getRepositoryClient(ctx context.Context, opts *SecureFlagOpts, ref registry.Reference) (*remote.Repository, error) { authClient, insecureRegistry, err := getAuthClient(ctx, opts, ref, true) if err != nil { return nil, err } return &remote.Repository{ Client: authClient, Reference: ref, PlainHTTP: insecureRegistry, }, nil } func getRegistryLoginClient(ctx context.Context, opts *SecureFlagOpts, serverAddress string) (*remote.Registry, error) { reg, err := remote.NewRegistry(serverAddress) if err != nil { return nil, err } reg.Client, reg.PlainHTTP, err = getAuthClient(ctx, opts, reg.Reference, false) if err != nil { return nil, err } return reg, nil } // getAuthClient returns an *auth.Client and a bool indicating if the registry // is insecure. // // If withCredential is true, the returned *auth.Client will have its Credential // function configured. // // If withCredential is false, the returned *auth.Client will have a nil // Credential function. func getAuthClient(ctx context.Context, opts *SecureFlagOpts, ref registry.Reference, withCredential bool) (*auth.Client, bool, error) { var insecureRegistry bool if opts.InsecureRegistry { insecureRegistry = opts.InsecureRegistry } else { insecureRegistry = configutil.IsRegistryInsecure(ref.Registry) if !insecureRegistry { if host, _, _ := net.SplitHostPort(ref.Registry); host == "localhost" { insecureRegistry = true } } } // build authClient authClient := httputil.NewAuthClient(ctx, nil) if !withCredential { return authClient, insecureRegistry, nil } cred := opts.Credential() if cred != auth.EmptyCredential { // use the specified credential authClient.Credential = auth.StaticCredential(ref.Host(), cred) } else { // use saved credentials credsStore, err := notationauth.NewCredentialsStore() if err != nil { return nil, false, fmt.Errorf("failed to get credentials store: %w", err) } authClient.Credential = credentials.Credential(credsStore) } return authClient, insecureRegistry, nil } notation-1.2.0/cmd/notation/registry_test.go000066400000000000000000000061611466375446100212250ustar00rootroot00000000000000// Copyright The Notary Project Authors. // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package main import ( "context" "net/http" "net/http/httptest" "net/url" "testing" ) const ( zeroDigest = "sha256:0000000000000000000000000000000000000000000000000000000000000000" ) func TestRegistry_getRemoteRepositoryWithReferrersAPISupported(t *testing.T) { ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.Method == http.MethodGet && r.URL.Path == "/v2/test/v1/referrers/"+zeroDigest { w.WriteHeader(http.StatusOK) w.Write([]byte(`{ "test": "TEST" }`)) return } t.Errorf("unexpected access: %s %q", r.Method, r.URL) w.WriteHeader(http.StatusNotFound) })) defer ts.Close() uri, err := url.Parse(ts.URL) if err != nil { t.Fatalf("invalid test http server: %v", err) } secureOpts := SecureFlagOpts{ InsecureRegistry: true, } _, err = getRemoteRepository(context.Background(), &secureOpts, uri.Host+"/test:v1", true) if err != nil { t.Errorf("getRemoteRepository() expected nil error, but got error: %v", err) } } func TestRegistry_getRemoteRepositoryWithReferrersAPINotSupported(t *testing.T) { ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.Method == http.MethodGet && r.URL.Path == "/v2/test/v1/referrers/"+zeroDigest { w.WriteHeader(http.StatusNotFound) return } t.Errorf("unexpected access: %s %q", r.Method, r.URL) w.WriteHeader(http.StatusNotFound) })) defer ts.Close() uri, err := url.Parse(ts.URL) if err != nil { t.Fatalf("invalid test http server: %v", err) } secureOpts := SecureFlagOpts{ InsecureRegistry: true, } _, err = getRemoteRepository(context.Background(), &secureOpts, uri.Host+"/test:v1", true) if err != nil { t.Errorf("getRemoteRepository() expected nil error, but got error: %v", err) } } func TestRegistry_getRemoteRepositoryWithReferrersTagSchema(t *testing.T) { ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.Method == http.MethodGet && r.URL.Path == "/v2/test/v1/referrers/"+zeroDigest { w.WriteHeader(http.StatusOK) w.Write([]byte(`{ "test": "TEST" }`)) return } t.Errorf("unexpected access: %s %q", r.Method, r.URL) w.WriteHeader(http.StatusNotFound) })) defer ts.Close() uri, err := url.Parse(ts.URL) if err != nil { t.Fatalf("invalid test http server: %v", err) } secureOpts := SecureFlagOpts{ InsecureRegistry: true, } _, err = getRemoteRepository(context.Background(), &secureOpts, uri.Host+"/test:v1", false) if err != nil { t.Errorf("getRemoteRepository() expected nil error, but got error: %v", err) } } notation-1.2.0/cmd/notation/sign.go000066400000000000000000000243741466375446100172640ustar00rootroot00000000000000// Copyright The Notary Project Authors. // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package main import ( "crypto/x509" "errors" "fmt" "net/http" "os" "strings" "time" corex509 "github.com/notaryproject/notation-core-go/x509" "github.com/notaryproject/notation-go" "github.com/notaryproject/notation-go/log" "github.com/notaryproject/notation/cmd/notation/internal/experimental" "github.com/notaryproject/notation/internal/cmd" "github.com/notaryproject/notation/internal/envelope" "github.com/notaryproject/notation/internal/httputil" nx509 "github.com/notaryproject/notation/internal/x509" "github.com/notaryproject/tspclient-go" ocispec "github.com/opencontainers/image-spec/specs-go/v1" "github.com/spf13/cobra" "golang.org/x/net/context" ) const referrersTagSchemaDeleteError = "failed to delete dangling referrers index" // timestampingTimeout is the timeout when requesting timestamp countersignature // from a TSA const timestampingTimeout = 15 * time.Second type signOpts struct { cmd.LoggingFlagOpts cmd.SignerFlagOpts SecureFlagOpts expiry time.Duration pluginConfig []string userMetadata []string reference string allowReferrersAPI bool forceReferrersTag bool ociLayout bool inputType inputType tsaServerURL string tsaRootCertificatePath string } func signCommand(opts *signOpts) *cobra.Command { if opts == nil { opts = &signOpts{ inputType: inputTypeRegistry, // remote registry by default } } longMessage := `Sign artifacts Note: a signing key must be specified. This can be done temporarily by specifying a key ID, or a new key can be configured using the command "notation key add" Example - Sign an OCI artifact using the default signing key, with the default JWS envelope, and use OCI image manifest to store the signature: notation sign /@ Example - Sign an OCI artifact using the default signing key, with the COSE envelope: notation sign --signature-format cose /@ Example - Sign an OCI artifact with a specified plugin and signing key stored in KMS notation sign --plugin --id /@ Example - Sign an OCI artifact using a specified key notation sign --key /@ Example - Sign an OCI artifact identified by a tag (Notation will resolve tag to digest) notation sign /: Example - Sign an OCI artifact stored in a registry and specify the signature expiry duration, for example 24 hours notation sign --expiry 24h /@ Example - Sign an OCI artifact and store signature using the Referrers API. If it's not supported, fallback to the Referrers tag schema notation sign --force-referrers-tag=false /@ Example - Sign an OCI artifact with timestamping: notation sign --timestamp-url --timestamp-root-cert /@ ` experimentalExamples := ` Example - [Experimental] Sign an OCI artifact referenced in an OCI layout notation sign --oci-layout "@" Example - [Experimental] Sign an OCI artifact identified by a tag and referenced in an OCI layout notation sign --oci-layout ":" ` command := &cobra.Command{ Use: "sign [flags] ", Short: "Sign artifacts", Long: longMessage, Args: func(cmd *cobra.Command, args []string) error { if len(args) == 0 { return errors.New("missing reference to the artifact: use `notation sign --help` to see what parameters are required") } opts.reference = args[0] return nil }, PreRunE: func(cmd *cobra.Command, args []string) error { if opts.ociLayout { opts.inputType = inputTypeOCILayout } return experimental.CheckFlagsAndWarn(cmd, "allow-referrers-api", "oci-layout") }, RunE: func(cmd *cobra.Command, args []string) error { // timestamping if cmd.Flags().Changed("timestamp-url") { if opts.tsaServerURL == "" { return errors.New("timestamping: tsa url cannot be empty") } if opts.tsaRootCertificatePath == "" { return errors.New("timestamping: tsa root certificate path cannot be empty") } } // allow-referrers-api flag is set if cmd.Flags().Changed("allow-referrers-api") { if opts.allowReferrersAPI { fmt.Fprintln(os.Stderr, "Warning: flag '--allow-referrers-api' is deprecated and will be removed in future versions, use '--force-referrers-tag=false' instead.") opts.forceReferrersTag = false } else { fmt.Fprintln(os.Stderr, "Warning: flag '--allow-referrers-api' is deprecated and will be removed in future versions.") } } return runSign(cmd, opts) }, } opts.LoggingFlagOpts.ApplyFlags(command.Flags()) opts.SignerFlagOpts.ApplyFlagsToCommand(command) opts.SecureFlagOpts.ApplyFlags(command.Flags()) cmd.SetPflagExpiry(command.Flags(), &opts.expiry) cmd.SetPflagPluginConfig(command.Flags(), &opts.pluginConfig) cmd.SetPflagUserMetadata(command.Flags(), &opts.userMetadata, cmd.PflagUserMetadataSignUsage) cmd.SetPflagReferrersAPI(command.Flags(), &opts.allowReferrersAPI, fmt.Sprintf(cmd.PflagReferrersUsageFormat, "sign")) command.Flags().StringVar(&opts.tsaServerURL, "timestamp-url", "", "RFC 3161 Timestamping Authority (TSA) server URL") command.Flags().StringVar(&opts.tsaRootCertificatePath, "timestamp-root-cert", "", "filepath of timestamp authority root certificate") cmd.SetPflagReferrersTag(command.Flags(), &opts.forceReferrersTag, "force to store signatures using the referrers tag schema") command.Flags().BoolVar(&opts.ociLayout, "oci-layout", false, "[Experimental] sign the artifact stored as OCI image layout") command.MarkFlagsMutuallyExclusive("oci-layout", "force-referrers-tag", "allow-referrers-api") command.MarkFlagsRequiredTogether("timestamp-url", "timestamp-root-cert") experimental.HideFlags(command, experimentalExamples, []string{"oci-layout"}) return command } func runSign(command *cobra.Command, cmdOpts *signOpts) error { // set log level ctx := cmdOpts.LoggingFlagOpts.InitializeLogger(command.Context()) // initialize signer, err := cmd.GetSigner(ctx, &cmdOpts.SignerFlagOpts) if err != nil { return err } sigRepo, err := getRepository(ctx, cmdOpts.inputType, cmdOpts.reference, &cmdOpts.SecureFlagOpts, cmdOpts.forceReferrersTag) if err != nil { return err } signOpts, err := prepareSigningOpts(ctx, cmdOpts) if err != nil { return err } manifestDesc, resolvedRef, err := resolveReference(ctx, cmdOpts.inputType, cmdOpts.reference, sigRepo, func(ref string, manifestDesc ocispec.Descriptor) { fmt.Fprintf(os.Stderr, "Warning: Always sign the artifact using digest(@sha256:...) rather than a tag(:%s) because tags are mutable and a tag reference can point to a different artifact than the one signed.\n", ref) }) if err != nil { return err } signOpts.ArtifactReference = manifestDesc.Digest.String() // core process _, err = notation.Sign(ctx, signer, sigRepo, signOpts) if err != nil { var errorPushSignatureFailed notation.ErrorPushSignatureFailed if errors.As(err, &errorPushSignatureFailed) && strings.Contains(err.Error(), referrersTagSchemaDeleteError) { fmt.Fprintln(os.Stderr, "Warning: Removal of outdated referrers index from remote registry failed. Garbage collection may be required.") // write out fmt.Println("Successfully signed", resolvedRef) return nil } return err } fmt.Println("Successfully signed", resolvedRef) return nil } func prepareSigningOpts(ctx context.Context, opts *signOpts) (notation.SignOptions, error) { logger := log.GetLogger(ctx) mediaType, err := envelope.GetEnvelopeMediaType(opts.SignerFlagOpts.SignatureFormat) if err != nil { return notation.SignOptions{}, err } pluginConfig, err := cmd.ParseFlagMap(opts.pluginConfig, cmd.PflagPluginConfig.Name) if err != nil { return notation.SignOptions{}, err } userMetadata, err := cmd.ParseFlagMap(opts.userMetadata, cmd.PflagUserMetadata.Name) if err != nil { return notation.SignOptions{}, err } signOpts := notation.SignOptions{ SignerSignOptions: notation.SignerSignOptions{ SignatureMediaType: mediaType, ExpiryDuration: opts.expiry, PluginConfig: pluginConfig, }, UserMetadata: userMetadata, } if opts.tsaServerURL != "" { // timestamping logger.Infof("Configured to timestamp with TSA %q", opts.tsaServerURL) signOpts.Timestamper, err = tspclient.NewHTTPTimestamper(httputil.NewClient(ctx, &http.Client{Timeout: timestampingTimeout}), opts.tsaServerURL) if err != nil { return notation.SignOptions{}, fmt.Errorf("cannot get http timestamper for timestamping: %w", err) } rootCerts, err := corex509.ReadCertificateFile(opts.tsaRootCertificatePath) if err != nil { return notation.SignOptions{}, err } if len(rootCerts) == 0 { return notation.SignOptions{}, fmt.Errorf("cannot find any certificate from %q. Expecting single x509 root certificate in PEM or DER format from the file", opts.tsaRootCertificatePath) } if len(rootCerts) > 1 { return notation.SignOptions{}, fmt.Errorf("found more than one certificates from %q. Expecting single x509 root certificate in PEM or DER format from the file", opts.tsaRootCertificatePath) } tsaRootCert := rootCerts[0] isRoot, err := nx509.IsRootCertificate(tsaRootCert) if err != nil { return notation.SignOptions{}, fmt.Errorf("failed to check root certificate with error: %w", err) } if !isRoot { return notation.SignOptions{}, fmt.Errorf("certificate from %q is not a root certificate. Expecting single x509 root certificate in PEM or DER format from the file", opts.tsaRootCertificatePath) } rootCAs := x509.NewCertPool() rootCAs.AddCert(tsaRootCert) signOpts.TSARootCAs = rootCAs } return signOpts, nil } notation-1.2.0/cmd/notation/sign_test.go000066400000000000000000000254061466375446100203200ustar00rootroot00000000000000// Copyright The Notary Project Authors. // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package main import ( "fmt" "reflect" "testing" "time" "github.com/notaryproject/notation/internal/cmd" "github.com/notaryproject/notation/internal/envelope" ) func TestSignCommand_BasicArgs(t *testing.T) { opts := &signOpts{} command := signCommand(opts) expected := &signOpts{ reference: "ref", SecureFlagOpts: SecureFlagOpts{ Username: "user", Password: "password", }, SignerFlagOpts: cmd.SignerFlagOpts{ Key: "key", SignatureFormat: envelope.JWS, }, forceReferrersTag: true, } if err := command.ParseFlags([]string{ expected.reference, "-u", expected.Username, "--password", expected.Password, "--key", expected.Key}); err != nil { t.Fatalf("Parse Flag failed: %v", err) } if err := command.Args(command, command.Flags().Args()); err != nil { t.Fatalf("Parse args failed: %v", err) } if !reflect.DeepEqual(*expected, *opts) { t.Fatalf("Expect sign opts: %v, got: %v", expected, opts) } } func TestSignCommand_MoreArgs(t *testing.T) { opts := &signOpts{} command := signCommand(opts) expected := &signOpts{ reference: "ref", SecureFlagOpts: SecureFlagOpts{ Username: "user", Password: "password", InsecureRegistry: true, }, SignerFlagOpts: cmd.SignerFlagOpts{ Key: "key", SignatureFormat: envelope.COSE, }, expiry: 24 * time.Hour, forceReferrersTag: true, } if err := command.ParseFlags([]string{ expected.reference, "-u", expected.Username, "-p", expected.Password, "--key", expected.Key, "--insecure-registry", "--signature-format", expected.SignerFlagOpts.SignatureFormat, "--expiry", expected.expiry.String(), "--force-referrers-tag", }); err != nil { t.Fatalf("Parse Flag failed: %v", err) } if err := command.Args(command, command.Flags().Args()); err != nil { t.Fatalf("Parse args failed: %v", err) } if !reflect.DeepEqual(*expected, *opts) { t.Fatalf("Expect sign opts: %v, got: %v", expected, opts) } } func TestSignCommand_CorrectConfig(t *testing.T) { opts := &signOpts{} command := signCommand(opts) expected := &signOpts{ reference: "ref", SignerFlagOpts: cmd.SignerFlagOpts{ Key: "key", SignatureFormat: envelope.COSE, }, expiry: 365 * 24 * time.Hour, pluginConfig: []string{"key0=val0", "key1=val1"}, forceReferrersTag: false, } if err := command.ParseFlags([]string{ expected.reference, "--key", expected.Key, "--signature-format", expected.SignerFlagOpts.SignatureFormat, "--expiry", expected.expiry.String(), "--plugin-config", "key0=val0", "--plugin-config", "key1=val1", "--force-referrers-tag=false", }); err != nil { t.Fatalf("Parse Flag failed: %v", err) } if err := command.Args(command, command.Flags().Args()); err != nil { t.Fatalf("Parse args failed: %v", err) } if !reflect.DeepEqual(*expected, *opts) { t.Fatalf("Expect sign opts: %v, got: %v", expected, opts) } config, err := cmd.ParseFlagMap(opts.pluginConfig, cmd.PflagPluginConfig.Name) if err != nil { t.Fatalf("Parse plugin Config flag failed: %v", err) } if len(config) != 2 { t.Fatalf("Expect plugin config number: %v, got: %v ", 2, len(config)) } for i := 0; i < 2; i++ { key, val := fmt.Sprintf("key%v", i), fmt.Sprintf("val%v", i) configVal, ok := config[key] if !ok { t.Fatalf("Key: %v not in config", key) } if val != configVal { t.Fatalf("Value for key: %v error, got: %v, expect: %v", key, configVal, val) } } } func TestSignCommmand_OnDemandKeyOptions(t *testing.T) { opts := &signOpts{} command := signCommand(opts) expected := &signOpts{ reference: "ref", SecureFlagOpts: SecureFlagOpts{ Username: "user", Password: "password", }, SignerFlagOpts: cmd.SignerFlagOpts{ KeyID: "keyID", PluginName: "pluginName", SignatureFormat: envelope.JWS, }, } if err := command.ParseFlags([]string{ expected.reference, "-u", expected.Username, "--password", expected.Password, "--id", expected.KeyID, "--plugin", expected.PluginName, "--force-referrers-tag=false", }); err != nil { t.Fatalf("Parse Flag failed: %v", err) } if err := command.Args(command, command.Flags().Args()); err != nil { t.Fatalf("Parse args failed: %v", err) } if !reflect.DeepEqual(*expected, *opts) { t.Fatalf("Expect sign opts: %v, got: %v", expected, opts) } } func TestSignCommmand_OnDemandKeyBadOptions(t *testing.T) { t.Run("error when using id and plugin options with key", func(t *testing.T) { opts := &signOpts{} command := signCommand(opts) expected := &signOpts{ reference: "ref", SecureFlagOpts: SecureFlagOpts{ Username: "user", Password: "password", }, SignerFlagOpts: cmd.SignerFlagOpts{ KeyID: "keyID", PluginName: "pluginName", Key: "keyName", SignatureFormat: envelope.JWS, }, } if err := command.ParseFlags([]string{ expected.reference, "-u", expected.Username, "--password", expected.Password, "--id", expected.KeyID, "--plugin", expected.PluginName, "--key", expected.Key, "--force-referrers-tag=false", }); err != nil { t.Fatalf("Parse Flag failed: %v", err) } if err := command.Args(command, command.Flags().Args()); err != nil { t.Fatalf("Parse args failed: %v", err) } if !reflect.DeepEqual(*expected, *opts) { t.Fatalf("Expect sign opts: %v, got: %v", expected, opts) } err := command.ValidateFlagGroups() if err == nil || err.Error() != "if any flags in the group [key id] are set none of the others can be; [id key] were all set" { t.Fatalf("Didn't get the expected error, but got: %v", err) } }) t.Run("error when using key and id options", func(t *testing.T) { opts := &signOpts{} command := signCommand(opts) expected := &signOpts{ reference: "ref", SecureFlagOpts: SecureFlagOpts{ Username: "user", Password: "password", }, SignerFlagOpts: cmd.SignerFlagOpts{ KeyID: "keyID", Key: "keyName", SignatureFormat: envelope.JWS, }, } if err := command.ParseFlags([]string{ expected.reference, "-u", expected.Username, "--password", expected.Password, "--id", expected.KeyID, "--key", expected.Key, "--force-referrers-tag=false", }); err != nil { t.Fatalf("Parse Flag failed: %v", err) } if err := command.Args(command, command.Flags().Args()); err != nil { t.Fatalf("Parse args failed: %v", err) } if !reflect.DeepEqual(*expected, *opts) { t.Fatalf("Expect sign opts: %v, got: %v", expected, opts) } err := command.ValidateFlagGroups() if err == nil || err.Error() != "if any flags in the group [id plugin] are set they must all be set; missing [plugin]" { t.Fatalf("Didn't get the expected error, but got: %v", err) } }) t.Run("error when using key and plugin options", func(t *testing.T) { opts := &signOpts{} command := signCommand(opts) expected := &signOpts{ reference: "ref", SecureFlagOpts: SecureFlagOpts{ Username: "user", Password: "password", }, SignerFlagOpts: cmd.SignerFlagOpts{ PluginName: "pluginName", Key: "keyName", SignatureFormat: envelope.JWS, }, } if err := command.ParseFlags([]string{ expected.reference, "-u", expected.Username, "--password", expected.Password, "--plugin", expected.PluginName, "--key", expected.Key, "--force-referrers-tag=false", }); err != nil { t.Fatalf("Parse Flag failed: %v", err) } if err := command.Args(command, command.Flags().Args()); err != nil { t.Fatalf("Parse args failed: %v", err) } if !reflect.DeepEqual(*expected, *opts) { t.Fatalf("Expect sign opts: %v, got: %v", expected, opts) } err := command.ValidateFlagGroups() if err == nil || err.Error() != "if any flags in the group [id plugin] are set they must all be set; missing [id]" { t.Fatalf("Didn't get the expected error, but got: %v", err) } }) t.Run("error when using id option and not plugin", func(t *testing.T) { opts := &signOpts{} command := signCommand(opts) expected := &signOpts{ reference: "ref", SecureFlagOpts: SecureFlagOpts{ Username: "user", Password: "password", }, SignerFlagOpts: cmd.SignerFlagOpts{ KeyID: "keyID", SignatureFormat: envelope.JWS, }, } if err := command.ParseFlags([]string{ expected.reference, "-u", expected.Username, "--password", expected.Password, "--id", expected.KeyID, "--force-referrers-tag=false", }); err != nil { t.Fatalf("Parse Flag failed: %v", err) } if err := command.Args(command, command.Flags().Args()); err != nil { t.Fatalf("Parse args failed: %v", err) } if !reflect.DeepEqual(*expected, *opts) { t.Fatalf("Expect sign opts: %v, got: %v", expected, opts) } err := command.ValidateFlagGroups() if err == nil || err.Error() != "if any flags in the group [id plugin] are set they must all be set; missing [plugin]" { t.Fatalf("Didn't get the expected error, but got: %v", err) } }) t.Run("error when using plugin option and not id", func(t *testing.T) { opts := &signOpts{} command := signCommand(opts) expected := &signOpts{ reference: "ref", SecureFlagOpts: SecureFlagOpts{ Username: "user", Password: "password", }, SignerFlagOpts: cmd.SignerFlagOpts{ PluginName: "pluginName", SignatureFormat: envelope.JWS, }, } if err := command.ParseFlags([]string{ expected.reference, "-u", expected.Username, "--password", expected.Password, "--plugin", expected.PluginName, "--force-referrers-tag=false", }); err != nil { t.Fatalf("Parse Flag failed: %v", err) } if err := command.Args(command, command.Flags().Args()); err != nil { t.Fatalf("Parse args failed: %v", err) } if !reflect.DeepEqual(*expected, *opts) { t.Fatalf("Expect sign opts: %v, got: %v", expected, opts) } err := command.ValidateFlagGroups() if err == nil || err.Error() != "if any flags in the group [id plugin] are set they must all be set; missing [id]" { t.Fatalf("Didn't get the expected error, but got: %v", err) } }) } func TestSignCommand_MissingArgs(t *testing.T) { cmd := signCommand(nil) if err := cmd.ParseFlags(nil); err != nil { t.Fatalf("Parse Flag failed: %v", err) } if err := cmd.Args(cmd, cmd.Flags().Args()); err == nil { t.Fatal("Parse Args expected error, but ok") } } notation-1.2.0/cmd/notation/verify.go000066400000000000000000000232551466375446100176250ustar00rootroot00000000000000// Copyright The Notary Project Authors. // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package main import ( "context" "errors" "fmt" "io/fs" "net/http" "os" "reflect" "time" "github.com/notaryproject/notation-core-go/revocation" "github.com/notaryproject/notation-core-go/revocation/purpose" "github.com/notaryproject/notation-go" "github.com/notaryproject/notation-go/dir" "github.com/notaryproject/notation-go/plugin" "github.com/notaryproject/notation-go/verifier" "github.com/notaryproject/notation-go/verifier/trustpolicy" "github.com/notaryproject/notation-go/verifier/truststore" "github.com/notaryproject/notation/cmd/notation/internal/experimental" "github.com/notaryproject/notation/internal/cmd" "github.com/notaryproject/notation/internal/httputil" "github.com/notaryproject/notation/internal/ioutil" "github.com/spf13/cobra" ) type verifyOpts struct { cmd.LoggingFlagOpts SecureFlagOpts reference string pluginConfig []string userMetadata []string allowReferrersAPI bool ociLayout bool trustPolicyScope string inputType inputType maxSignatureAttempts int } func verifyCommand(opts *verifyOpts) *cobra.Command { if opts == nil { opts = &verifyOpts{ inputType: inputTypeRegistry, // remote registry by default } } longMessage := `Verify OCI artifacts Prerequisite: added a certificate into trust store and created a trust policy. Example - Verify a signature on an OCI artifact identified by a digest: notation verify /@ Example - Verify a signature on an OCI artifact identified by a tag (Notation will resolve tag to digest): notation verify /: ` experimentalExamples := ` Example - [Experimental] Verify a signature on an OCI artifact referenced in an OCI layout using trust policy statement specified by scope. notation verify --oci-layout /@ --scope Example - [Experimental] Verify a signature on an OCI artifact identified by a tag and referenced in an OCI layout using trust policy statement specified by scope. notation verify --oci-layout /: --scope ` command := &cobra.Command{ Use: "verify [reference]", Short: "Verify OCI artifacts", Long: longMessage, Args: func(cmd *cobra.Command, args []string) error { if len(args) == 0 { return errors.New("missing reference to the artifact: use `notation verify --help` to see what parameters are required") } opts.reference = args[0] return nil }, PreRunE: func(cmd *cobra.Command, args []string) error { if opts.ociLayout { opts.inputType = inputTypeOCILayout } return experimental.CheckFlagsAndWarn(cmd, "allow-referrers-api", "oci-layout", "scope") }, RunE: func(cmd *cobra.Command, args []string) error { if opts.maxSignatureAttempts <= 0 { return fmt.Errorf("max-signatures value %d must be a positive number", opts.maxSignatureAttempts) } if cmd.Flags().Changed("allow-referrers-api") { fmt.Fprintln(os.Stderr, "Warning: flag '--allow-referrers-api' is deprecated and will be removed in future versions.") } return runVerify(cmd, opts) }, } opts.LoggingFlagOpts.ApplyFlags(command.Flags()) opts.SecureFlagOpts.ApplyFlags(command.Flags()) command.Flags().StringArrayVar(&opts.pluginConfig, "plugin-config", nil, "{key}={value} pairs that are passed as it is to a plugin, if the verification is associated with a verification plugin, refer plugin documentation to set appropriate values") cmd.SetPflagUserMetadata(command.Flags(), &opts.userMetadata, cmd.PflagUserMetadataVerifyUsage) cmd.SetPflagReferrersAPI(command.Flags(), &opts.allowReferrersAPI, fmt.Sprintf(cmd.PflagReferrersUsageFormat, "verify")) command.Flags().IntVar(&opts.maxSignatureAttempts, "max-signatures", 100, "maximum number of signatures to evaluate or examine") command.Flags().BoolVar(&opts.ociLayout, "oci-layout", false, "[Experimental] verify the artifact stored as OCI image layout") command.Flags().StringVar(&opts.trustPolicyScope, "scope", "", "[Experimental] set trust policy scope for artifact verification, required and can only be used when flag \"--oci-layout\" is set") command.MarkFlagsRequiredTogether("oci-layout", "scope") experimental.HideFlags(command, experimentalExamples, []string{"oci-layout", "scope"}) return command } func runVerify(command *cobra.Command, opts *verifyOpts) error { // set log level ctx := opts.LoggingFlagOpts.InitializeLogger(command.Context()) // initialize sigVerifier, err := getVerifier(ctx) if err != nil { return err } // set up verification plugin config. configs, err := cmd.ParseFlagMap(opts.pluginConfig, cmd.PflagPluginConfig.Name) if err != nil { return err } // set up user metadata userMetadata, err := cmd.ParseFlagMap(opts.userMetadata, cmd.PflagUserMetadata.Name) if err != nil { return err } // core verify process reference := opts.reference // always use the Referrers API, if not supported, automatically fallback to // the referrers tag schema sigRepo, err := getRepository(ctx, opts.inputType, reference, &opts.SecureFlagOpts, false) if err != nil { return err } // resolve the given reference and set the digest _, resolvedRef, err := resolveReferenceWithWarning(ctx, opts.inputType, reference, sigRepo, "verify") if err != nil { return err } intendedRef := resolveArtifactDigestReference(resolvedRef, opts.trustPolicyScope) verifyOpts := notation.VerifyOptions{ ArtifactReference: intendedRef, PluginConfig: configs, MaxSignatureAttempts: opts.maxSignatureAttempts, UserMetadata: userMetadata, } _, outcomes, err := notation.Verify(ctx, sigVerifier, sigRepo, verifyOpts) err = checkVerificationFailure(outcomes, resolvedRef, err) if err != nil { return err } reportVerificationSuccess(outcomes, resolvedRef) return nil } func checkVerificationFailure(outcomes []*notation.VerificationOutcome, printOut string, err error) error { // write out on failure if err != nil || len(outcomes) == 0 { if err != nil { var errTrustStore truststore.TrustStoreError if errors.As(err, &errTrustStore) { if errors.Is(err, fs.ErrNotExist) { return fmt.Errorf("%w. Use command 'notation cert add' to create and add trusted certificates to the trust store", errTrustStore) } else { return fmt.Errorf("%w. %w", errTrustStore, errTrustStore.InnerError) } } var errCertificate truststore.CertificateError if errors.As(err, &errCertificate) { if errors.Is(err, fs.ErrNotExist) { return fmt.Errorf("%w. Use command 'notation cert add' to create and add trusted certificates to the trust store", errCertificate) } else { return fmt.Errorf("%w. %w", errCertificate, errCertificate.InnerError) } } var errorVerificationFailed notation.ErrorVerificationFailed if !errors.As(err, &errorVerificationFailed) { return fmt.Errorf("signature verification failed: %w", err) } } return fmt.Errorf("signature verification failed for all the signatures associated with %s", printOut) } return nil } func reportVerificationSuccess(outcomes []*notation.VerificationOutcome, printout string) { // write out on success outcome := outcomes[0] // print out warning for any failed result with logged verification action for _, result := range outcome.VerificationResults { if result.Error != nil { // at this point, the verification action has to be logged and // it's failed fmt.Fprintf(os.Stderr, "Warning: %v was set to %q and failed with error: %v\n", result.Type, result.Action, result.Error) } } if reflect.DeepEqual(outcome.VerificationLevel, trustpolicy.LevelSkip) { fmt.Println("Trust policy is configured to skip signature verification for", printout) } else { fmt.Println("Successfully verified signature for", printout) printMetadataIfPresent(outcome) } } func printMetadataIfPresent(outcome *notation.VerificationOutcome) { // the signature envelope is parsed as part of verification. // since user metadata is only printed on successful verification, // this error can be ignored metadata, _ := outcome.UserMetadata() if len(metadata) > 0 { fmt.Println("\nThe artifact was signed with the following user metadata.") ioutil.PrintMetadataMap(os.Stdout, metadata) } } func getVerifier(ctx context.Context) (notation.Verifier, error) { policyDocument, err := trustpolicy.LoadDocument() if err != nil { return nil, err } x509TrustStore := truststore.NewX509TrustStore(dir.ConfigFS()) ocspHttpClient := httputil.NewClient(ctx, &http.Client{Timeout: 2 * time.Second}) revocationCodeSigningValidator, err := revocation.NewWithOptions(revocation.Options{ OCSPHTTPClient: ocspHttpClient, CertChainPurpose: purpose.CodeSigning, }) if err != nil { return nil, err } revocationTimestampingValidator, err := revocation.NewWithOptions(revocation.Options{ OCSPHTTPClient: ocspHttpClient, CertChainPurpose: purpose.Timestamping, }) if err != nil { return nil, err } return verifier.NewWithOptions(policyDocument, x509TrustStore, plugin.NewCLIManager(dir.PluginFS()), verifier.VerifierOptions{ RevocationCodeSigningValidator: revocationCodeSigningValidator, RevocationTimestampingValidator: revocationTimestampingValidator, }) } notation-1.2.0/cmd/notation/verify_test.go000066400000000000000000000055521466375446100206640ustar00rootroot00000000000000// Copyright The Notary Project Authors. // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package main import ( "context" "reflect" "testing" "github.com/notaryproject/notation-go/dir" ) func TestVerifyCommand_BasicArgs(t *testing.T) { opts := &verifyOpts{} command := verifyCommand(opts) expected := &verifyOpts{ reference: "ref", SecureFlagOpts: SecureFlagOpts{ Username: "user", Password: "password", }, pluginConfig: []string{"key1=val1"}, maxSignatureAttempts: 100, } if err := command.ParseFlags([]string{ expected.reference, "--username", expected.Username, "--password", expected.Password, "--plugin-config", "key1=val1"}); err != nil { t.Fatalf("Parse Flag failed: %v", err) } if err := command.Args(command, command.Flags().Args()); err != nil { t.Fatalf("Parse args failed: %v", err) } if !reflect.DeepEqual(*expected, *opts) { t.Fatalf("Expect verify opts: %v, got: %v", expected, opts) } } func TestVerifyCommand_MoreArgs(t *testing.T) { opts := &verifyOpts{} command := verifyCommand(opts) expected := &verifyOpts{ reference: "ref", SecureFlagOpts: SecureFlagOpts{ InsecureRegistry: true, }, pluginConfig: []string{"key1=val1", "key2=val2"}, maxSignatureAttempts: 100, } if err := command.ParseFlags([]string{ expected.reference, "--insecure-registry", "--plugin-config", "key1=val1", "--plugin-config", "key2=val2"}); err != nil { t.Fatalf("Parse Flag failed: %v", err) } if err := command.Args(command, command.Flags().Args()); err != nil { t.Fatalf("Parse args failed: %v", err) } if !reflect.DeepEqual(*expected, *opts) { t.Fatalf("Expect verify opts: %v, got: %v", expected, opts) } } func TestVerifyCommand_MissingArgs(t *testing.T) { cmd := verifyCommand(nil) if err := cmd.ParseFlags(nil); err != nil { t.Fatalf("Parse Flag failed: %v", err) } if err := cmd.Args(cmd, cmd.Flags().Args()); err == nil { t.Fatal("Parse Args expected error, but ok") } } func TestGetVerifier(t *testing.T) { t.Run("non-existing trust policy", func(t *testing.T) { dir.UserConfigDir = "/" expectedErrMsg := "trust policy is not present. To create a trust policy, see: https://notaryproject.dev/docs/quickstart/#create-a-trust-policy" _, err := getVerifier(context.Background()) if err == nil || err.Error() != expectedErrMsg { t.Fatalf("expected %s, but got %s", expectedErrMsg, err) } }) } notation-1.2.0/cmd/notation/version.go000066400000000000000000000023361466375446100200030ustar00rootroot00000000000000// Copyright The Notary Project Authors. // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package main import ( "fmt" "runtime" "github.com/notaryproject/notation/internal/version" "github.com/spf13/cobra" ) func versionCommand() *cobra.Command { cmd := &cobra.Command{ Use: "version", Short: "Show the notation version information", Args: cobra.NoArgs, Run: func(cmd *cobra.Command, args []string) { runVersion() }, } return cmd } func runVersion() { fmt.Printf("Notation - a tool to sign and verify artifacts.\n\n") fmt.Printf("Version: %s\n", version.GetVersion()) fmt.Printf("Go version: %s\n", runtime.Version()) if version.GitCommit != "" { fmt.Printf("Git commit: %s\n", version.GitCommit) } } notation-1.2.0/go.mod000066400000000000000000000022331466375446100144730ustar00rootroot00000000000000module github.com/notaryproject/notation go 1.23 require ( github.com/notaryproject/notation-core-go v1.1.0 github.com/notaryproject/notation-go v1.2.0 github.com/notaryproject/tspclient-go v0.2.0 github.com/opencontainers/go-digest v1.0.0 github.com/opencontainers/image-spec v1.1.0 github.com/sirupsen/logrus v1.9.3 github.com/spf13/cobra v1.8.1 github.com/spf13/pflag v1.0.5 golang.org/x/net v0.28.0 golang.org/x/term v0.23.0 oras.land/oras-go/v2 v2.5.0 ) require ( github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 // indirect github.com/fxamacker/cbor/v2 v2.7.0 // indirect github.com/go-asn1-ber/asn1-ber v1.5.5 // indirect github.com/go-ldap/ldap/v3 v3.4.8 // indirect github.com/golang-jwt/jwt/v4 v4.5.0 // indirect github.com/google/uuid v1.6.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/notaryproject/notation-plugin-framework-go v1.0.0 // indirect github.com/veraison/go-cose v1.1.0 // indirect github.com/x448/float16 v0.8.4 // indirect golang.org/x/crypto v0.26.0 // indirect golang.org/x/mod v0.20.0 // indirect golang.org/x/sync v0.6.0 // indirect golang.org/x/sys v0.23.0 // indirect ) notation-1.2.0/go.sum000066400000000000000000000301071466375446100145210ustar00rootroot00000000000000github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 h1:mFRzDkZVAjdal+s7s0MwaRv9igoPqLRdzOLzw/8Xvq8= github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU= github.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa h1:LHTHcTQiSGT7VVbI0o4wBRNQIgn917usHWOd6VAffYI= github.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa/go.mod h1:cEWa1LVoE5KvSD9ONXsZrj0z6KqySlCCNKHlLzbqAt4= github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E= github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= github.com/go-asn1-ber/asn1-ber v1.5.5 h1:MNHlNMBDgEKD4TcKr36vQN68BA00aDfjIt3/bD50WnA= github.com/go-asn1-ber/asn1-ber v1.5.5/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0= github.com/go-ldap/ldap/v3 v3.4.8 h1:loKJyspcRezt2Q3ZRMq2p/0v8iOurlmeXDPw6fikSvQ= github.com/go-ldap/ldap/v3 v3.4.8/go.mod h1:qS3Sjlu76eHfHGpUdWkAXQTw4beih+cHsco2jXlIXrk= github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg= github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4= github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM= github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8= github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/jcmturner/aescts/v2 v2.0.0 h1:9YKLH6ey7H4eDBXW8khjYslgyqG2xZikXP0EQFKrle8= github.com/jcmturner/aescts/v2 v2.0.0/go.mod h1:AiaICIRyfYg35RUkr8yESTqvSy7csK90qZ5xfvvsoNs= github.com/jcmturner/dnsutils/v2 v2.0.0 h1:lltnkeZGL0wILNvrNiVCR6Ro5PGU/SeBvVO/8c/iPbo= github.com/jcmturner/dnsutils/v2 v2.0.0/go.mod h1:b0TnjGOvI/n42bZa+hmXL+kFJZsFT7G4t3HTlQ184QM= github.com/jcmturner/gofork v1.7.6 h1:QH0l3hzAU1tfT3rZCnW5zXl+orbkNMMRGJfdJjHVETg= github.com/jcmturner/gofork v1.7.6/go.mod h1:1622LH6i/EZqLloHfE7IeZ0uEJwMSUyQ/nDd82IeqRo= github.com/jcmturner/goidentity/v6 v6.0.1 h1:VKnZd2oEIMorCTsFBnJWbExfNN7yZr3EhJAxwOkZg6o= github.com/jcmturner/goidentity/v6 v6.0.1/go.mod h1:X1YW3bgtvwAXju7V3LCIMpY0Gbxyjn/mY9zx4tFonSg= github.com/jcmturner/gokrb5/v8 v8.4.4 h1:x1Sv4HaTpepFkXbt2IkL29DXRf8sOfZXo8eRKh687T8= github.com/jcmturner/gokrb5/v8 v8.4.4/go.mod h1:1btQEpgT6k+unzCwX1KdWMEwPPkkgBtP+F6aCACiMrs= github.com/jcmturner/rpc/v2 v2.0.3 h1:7FXXj8Ti1IaVFpSAziCZWNzbNuZmnvw/i6CqLNdWfZY= github.com/jcmturner/rpc/v2 v2.0.3/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJki3ELg/Hc= github.com/notaryproject/notation-core-go v1.1.0 h1:xCybcONOKcCyPNihJUSa+jRNsyQFNkrk0eJVVs1kWeg= github.com/notaryproject/notation-core-go v1.1.0/go.mod h1:+6AOh41JPrnVLbW/19SJqdhVHwKgIINBO/np0e7nXJA= github.com/notaryproject/notation-go v1.2.0 h1:Muq/S+Vyyerq/hefD1SUaIqFbNrhV/zgXi/M9sL4bpg= github.com/notaryproject/notation-go v1.2.0/go.mod h1:re9V+TfuNRaUq5e3NuNcCJN53++sL2KbnJrjGyOUpgE= github.com/notaryproject/notation-plugin-framework-go v1.0.0 h1:6Qzr7DGXoCgXEQN+1gTZWuJAZvxh3p8Lryjn5FaLzi4= github.com/notaryproject/notation-plugin-framework-go v1.0.0/go.mod h1:RqWSrTOtEASCrGOEffq0n8pSg2KOgKYiWqFWczRSics= github.com/notaryproject/tspclient-go v0.2.0 h1:g/KpQGmyk/h7j60irIRG1mfWnibNOzJ8WhLqAzuiQAQ= github.com/notaryproject/tspclient-go v0.2.0/go.mod h1:LGyA/6Kwd2FlM0uk8Vc5il3j0CddbWSHBj/4kxQDbjs= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug= github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/veraison/go-cose v1.1.0 h1:AalPS4VGiKavpAzIlBjrn7bhqXiXi4jbMYY/2+UC+4o= github.com/veraison/go-cose v1.1.0/go.mod h1:7ziE85vSq4ScFTg6wyoMXjucIGOf4JkFEZi/an96Ct4= github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw= golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0= golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE= golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.23.0 h1:YfKFowiIMvtgl1UERQoTPPToxltDeZfbj4H7dVUCwmM= golang.org/x/sys v0.23.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58= golang.org/x/term v0.23.0 h1:F6D4vR+EHoL9/sWAWgAR1H2DcHr4PareCbAaCo1RpuU= golang.org/x/term v0.23.0/go.mod h1:DgV24QBUrK6jhZXl+20l6UWznPlwAHm1Q1mGHtydmSk= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= oras.land/oras-go/v2 v2.5.0 h1:o8Me9kLY74Vp5uw07QXPiitjsw7qNXi8Twd+19Zf02c= oras.land/oras-go/v2 v2.5.0/go.mod h1:z4eisnLP530vwIOUOJeBIj0aGI0L1C3d53atvCBqZHg= notation-1.2.0/internal/000077500000000000000000000000001466375446100152015ustar00rootroot00000000000000notation-1.2.0/internal/auth/000077500000000000000000000000001466375446100161425ustar00rootroot00000000000000notation-1.2.0/internal/auth/credentials.go000066400000000000000000000035721466375446100207750ustar00rootroot00000000000000// Copyright The Notary Project Authors. // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package auth import ( "fmt" "github.com/notaryproject/notation-go/dir" "oras.land/oras-go/v2/registry/remote/credentials" ) // NewCredentialsStore returns a new credentials store from the settings in the // configuration file. func NewCredentialsStore() (credentials.Store, error) { configPath, err := dir.ConfigFS().SysPath(dir.PathConfigFile) if err != nil { return nil, fmt.Errorf("failed to load config file: %w", err) } // use notation config opts := credentials.StoreOptions{AllowPlaintextPut: false} notationStore, err := credentials.NewStore(configPath, opts) if err != nil { return nil, fmt.Errorf("failed to create credential store from config file: %w", err) } if notationStore.IsAuthConfigured() { return notationStore, nil } // use docker config dockerStore, err := credentials.NewStoreFromDocker(opts) if err != nil { return nil, fmt.Errorf("failed to create credential store from docker config file: %w", err) } if dockerStore.IsAuthConfigured() { return dockerStore, nil } // detect platform-default native store if osDefaultStore, ok := credentials.NewDefaultNativeStore(); ok { return osDefaultStore, nil } // if the default store is not available, still use notation store so that // there won't be errors when getting credentials return notationStore, nil } notation-1.2.0/internal/cmd/000077500000000000000000000000001466375446100157445ustar00rootroot00000000000000notation-1.2.0/internal/cmd/flags.go000066400000000000000000000121561466375446100173740ustar00rootroot00000000000000// Copyright The Notary Project Authors. // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // Package cmd contains common flags and routines for all CLIs. package cmd import ( "fmt" "strings" "time" "github.com/notaryproject/notation/internal/envelope" "github.com/notaryproject/notation/pkg/configutil" "github.com/spf13/pflag" ) const ( OutputPlaintext = "text" OutputJSON = "json" ) var ( PflagKey = &pflag.Flag{ Name: "key", Shorthand: "k", Usage: "signing key name, for a key previously added to notation's key list. This is mutually exclusive with the --id and --plugin flags", } SetPflagKey = func(fs *pflag.FlagSet, p *string) { fs.StringVarP(p, PflagKey.Name, PflagKey.Shorthand, "", PflagKey.Usage) } PflagSignatureFormat = &pflag.Flag{ Name: "signature-format", Usage: "signature envelope format, options: \"jws\", \"cose\"", } SetPflagSignatureFormat = func(fs *pflag.FlagSet, p *string) { defaultSignatureFormat := envelope.JWS // load config to get signatureFormat config, err := configutil.LoadConfigOnce() if err == nil && config.SignatureFormat != "" { defaultSignatureFormat = config.SignatureFormat } fs.StringVar(p, PflagSignatureFormat.Name, defaultSignatureFormat, PflagSignatureFormat.Usage) } PflagID = &pflag.Flag{ Name: "id", Usage: "key id (required if --plugin is set). This is mutually exclusive with the --key flag", } SetPflagID = func(fs *pflag.FlagSet, p *string) { fs.StringVar(p, PflagID.Name, "", PflagID.Usage) } PflagPlugin = &pflag.Flag{ Name: "plugin", Usage: "signing plugin name (required if --id is set). This is mutually exclusive with the --key flag", } SetPflagPlugin = func(fs *pflag.FlagSet, p *string) { fs.StringVar(p, PflagPlugin.Name, "", PflagPlugin.Usage) } PflagExpiry = &pflag.Flag{ Name: "expiry", Shorthand: "e", Usage: "optional expiry that provides a \"best by use\" time for the artifact. The duration is specified in minutes(m) and/or hours(h). For example: 12h, 30m, 3h20m", } SetPflagExpiry = func(fs *pflag.FlagSet, p *time.Duration) { fs.DurationVarP(p, PflagExpiry.Name, PflagExpiry.Shorthand, time.Duration(0), PflagExpiry.Usage) } PflagReference = &pflag.Flag{ Name: "reference", Shorthand: "r", Usage: "original reference", } SetPflagReference = func(fs *pflag.FlagSet, p *string) { fs.StringVarP(p, PflagReference.Name, PflagReference.Shorthand, "", PflagReference.Usage) } PflagPluginConfig = &pflag.Flag{ Name: "plugin-config", Usage: "{key}={value} pairs that are passed as it is to a plugin, refer plugin's documentation to set appropriate values", } SetPflagPluginConfig = func(fs *pflag.FlagSet, p *[]string) { fs.StringArrayVar(p, PflagPluginConfig.Name, nil, PflagPluginConfig.Usage) } PflagUserMetadata = &pflag.Flag{ Name: "user-metadata", Shorthand: "m", } PflagUserMetadataSignUsage = "{key}={value} pairs that are added to the signature payload" PflagUserMetadataVerifyUsage = "user defined {key}={value} pairs that must be present in the signature for successful verification if provided" SetPflagUserMetadata = func(fs *pflag.FlagSet, p *[]string, usage string) { fs.StringArrayVarP(p, PflagUserMetadata.Name, PflagUserMetadata.Shorthand, nil, usage) } PflagReferrersAPI = &pflag.Flag{ Name: "allow-referrers-api", } PflagReferrersUsageFormat = "[Experimental] use the Referrers API to %s signatures, if not supported (returns 404), fallback to the Referrers tag schema" SetPflagReferrersAPI = func(fs *pflag.FlagSet, p *bool, usage string) { fs.BoolVar(p, PflagReferrersAPI.Name, false, usage) fs.MarkHidden(PflagReferrersAPI.Name) } PflagReferrersTag = &pflag.Flag{ Name: "force-referrers-tag", } SetPflagReferrersTag = func(fs *pflag.FlagSet, p *bool, usage string) { fs.BoolVar(p, PflagReferrersTag.Name, true, usage) } PflagOutput = &pflag.Flag{ Name: "output", Shorthand: "o", } PflagOutputUsage = fmt.Sprintf("output format, options: '%s', '%s'", OutputJSON, OutputPlaintext) SetPflagOutput = func(fs *pflag.FlagSet, p *string, usage string) { fs.StringVarP(p, PflagOutput.Name, PflagOutput.Shorthand, OutputPlaintext, usage) } ) // KeyValueSlice is a flag with type int type KeyValueSlice interface { Set(value string) error String() string } func ParseFlagMap(c []string, flagName string) (map[string]string, error) { m := make(map[string]string, len(c)) for _, pair := range c { key, val, found := strings.Cut(pair, "=") if !found || key == "" || val == "" { return nil, fmt.Errorf("could not parse flag %s: key-value pair requires \"=\" as separator", flagName) } m[key] = val } return m, nil } notation-1.2.0/internal/cmd/options.go000066400000000000000000000057061466375446100177760ustar00rootroot00000000000000// Copyright The Notary Project Authors. // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package cmd import ( "context" "github.com/notaryproject/notation-go/log" "github.com/notaryproject/notation/internal/trace" "github.com/sirupsen/logrus" "github.com/spf13/cobra" "github.com/spf13/pflag" credentialstrace "oras.land/oras-go/v2/registry/remote/credentials/trace" ) // SignerFlagOpts cmd opts for using cmd.GetSigner type SignerFlagOpts struct { Key string SignatureFormat string KeyID string PluginName string } // ApplyFlags set flags and their default values for the FlagSet func (opts *SignerFlagOpts) ApplyFlagsToCommand(command *cobra.Command) { fs := command.Flags() SetPflagKey(fs, &opts.Key) SetPflagSignatureFormat(fs, &opts.SignatureFormat) SetPflagID(fs, &opts.KeyID) SetPflagPlugin(fs, &opts.PluginName) command.MarkFlagsRequiredTogether("id", "plugin") command.MarkFlagsMutuallyExclusive("key", "id") command.MarkFlagsMutuallyExclusive("key", "plugin") } // LoggingFlagOpts option struct. type LoggingFlagOpts struct { Debug bool Verbose bool } // ApplyFlags applies flags to a command flag set. func (opts *LoggingFlagOpts) ApplyFlags(fs *pflag.FlagSet) { fs.BoolVarP(&opts.Debug, "debug", "d", false, "debug mode") fs.BoolVarP(&opts.Verbose, "verbose", "v", false, "verbose mode") } // InitializeLogger sets up the logger based on common options. func (opts *LoggingFlagOpts) InitializeLogger(ctx context.Context) context.Context { if opts.Debug { ctx = trace.WithLoggerLevel(ctx, logrus.DebugLevel) } else if opts.Verbose { ctx = trace.WithLoggerLevel(ctx, logrus.InfoLevel) } else { return ctx } return withExecutableTrace(ctx) } // withExecutableTrace adds tracing for credential helper executables. func withExecutableTrace(ctx context.Context) context.Context { logger := log.GetLogger(ctx) ctx = credentialstrace.WithExecutableTrace(ctx, &credentialstrace.ExecutableTrace{ ExecuteStart: func(executableName, action string) { logger.Debugf("started executing credential helper program %s with action %s", executableName, action) }, ExecuteDone: func(executableName, action string, err error) { if err != nil { logger.Errorf("finished executing credential helper program %s with action %s and got error %w", executableName, action, err) } else { logger.Debugf("successfully finished executing credential helper program %s with action %s", executableName, action) } }, }) return ctx } notation-1.2.0/internal/cmd/signer.go000066400000000000000000000042241466375446100175640ustar00rootroot00000000000000// Copyright The Notary Project Authors. // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package cmd import ( "context" "errors" "github.com/notaryproject/notation-go" "github.com/notaryproject/notation-go/dir" "github.com/notaryproject/notation-go/plugin" "github.com/notaryproject/notation-go/signer" "github.com/notaryproject/notation/pkg/configutil" ) // GetSigner returns a signer according to the CLI context. func GetSigner(ctx context.Context, opts *SignerFlagOpts) (notation.Signer, error) { // Check if using on-demand key if opts.KeyID != "" && opts.PluginName != "" && opts.Key == "" { // Construct a signer from on-demand key mgr := plugin.NewCLIManager(dir.PluginFS()) plugin, err := mgr.Get(ctx, opts.PluginName) if err != nil { return nil, err } return signer.NewFromPlugin(plugin, opts.KeyID, map[string]string{}) } // Construct a signer from preconfigured key pair in config.json // if key name is provided as the CLI argument key, err := configutil.ResolveKey(opts.Key) if err != nil { return nil, err } if key.X509KeyPair != nil { return signer.NewFromFiles(key.X509KeyPair.KeyPath, key.X509KeyPair.CertificatePath) } // Construct a plugin signer if key name provided as the CLI argument // corresponds to an external key if key.ExternalKey != nil { mgr := plugin.NewCLIManager(dir.PluginFS()) plugin, err := mgr.Get(ctx, key.PluginName) if err != nil { return nil, err } return signer.NewFromPlugin(plugin, key.ExternalKey.ID, key.PluginConfig) } return nil, errors.New("unsupported key, either provide a local key and certificate file paths, or a key name in config.json, check [DOC_PLACEHOLDER] for details") } notation-1.2.0/internal/envelope/000077500000000000000000000000001466375446100170165ustar00rootroot00000000000000notation-1.2.0/internal/envelope/envelope.go000066400000000000000000000050111466375446100211570ustar00rootroot00000000000000// Copyright The Notary Project Authors. // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package envelope import ( "encoding/json" "errors" "fmt" "github.com/notaryproject/notation-core-go/signature" "github.com/notaryproject/notation-core-go/signature/cose" "github.com/notaryproject/notation-core-go/signature/jws" ocispec "github.com/opencontainers/image-spec/specs-go/v1" ) const ( // Supported envelope format. COSE = "cose" JWS = "jws" // MediaTypePayloadV1 is the supported content type for signature's payload. MediaTypePayloadV1 = "application/vnd.cncf.notary.payload.v1+json" ) // Payload describes the content that gets signed. type Payload struct { TargetArtifact ocispec.Descriptor `json:"targetArtifact"` } // GetEnvelopeMediaType converts the envelope type to mediaType name. func GetEnvelopeMediaType(sigFormat string) (string, error) { switch sigFormat { case JWS: return jws.MediaTypeEnvelope, nil case COSE: return cose.MediaTypeEnvelope, nil } return "", fmt.Errorf("signature format %q not supported\nSupported signature envelope formats are \"jws\" and \"cose\"", sigFormat) } // ValidatePayloadContentType validates signature payload's content type. func ValidatePayloadContentType(payload *signature.Payload) error { switch payload.ContentType { case MediaTypePayloadV1: return nil default: return fmt.Errorf("payload content type %q not supported", payload.ContentType) } } // DescriptorFromPayload parses a signature payload and returns the descriptor // that was signed. Note: the descriptor was signed but may not be trusted func DescriptorFromSignaturePayload(payload *signature.Payload) (*ocispec.Descriptor, error) { if payload == nil { return nil, errors.New("empty payload") } err := ValidatePayloadContentType(payload) if err != nil { return nil, err } var parsedPayload Payload err = json.Unmarshal(payload.Content, &parsedPayload) if err != nil { return nil, errors.New("failed to unmarshall the payload content to Payload") } return &parsedPayload.TargetArtifact, nil } notation-1.2.0/internal/envelope/envelope_test.go000066400000000000000000000062131466375446100222230ustar00rootroot00000000000000// Copyright The Notary Project Authors. // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package envelope import ( "testing" "github.com/notaryproject/notation-core-go/signature" ) func TestGetEnvelopeMediaType(t *testing.T) { type args struct { sigFormat string } tests := []struct { name string args args want string wantErr bool }{ { name: "jws", args: args{"jws"}, want: "application/jose+json", wantErr: false, }, { name: "cose", args: args{"cose"}, want: "application/cose", wantErr: false, }, { name: "unsupported", args: args{"unsupported"}, want: "", wantErr: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got, err := GetEnvelopeMediaType(tt.args.sigFormat) if (err != nil) != tt.wantErr { t.Errorf("GetEnvelopeMediaType() error = %v, wantErr %v", err, tt.wantErr) return } if got != tt.want { t.Errorf("GetEnvelopeMediaType() = %v, want %v", got, tt.want) } }) } } func TestValidatePayloadContentType(t *testing.T) { tests := []struct { name string payload *signature.Payload wantErr bool }{ { name: "valid content type", payload: &signature.Payload{ ContentType: MediaTypePayloadV1, }, wantErr: false, }, { name: "invalid content type", payload: &signature.Payload{ ContentType: "invalid", }, wantErr: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { err := ValidatePayloadContentType(tt.payload) if (err != nil) != tt.wantErr { t.Errorf("ValidatePayloadContentType() error = %v, wantErr %v", err, tt.wantErr) } }) } } func TestDescriptorFromSignaturePayload(t *testing.T) { validPayload := &signature.Payload{ ContentType: MediaTypePayloadV1, Content: []byte(`{"targetArtifact": {"mediaType": "application/vnd.oci.image.manifest.v1+json", "size": 314159, "digest": "sha256:abcd1234", "urls": ["http://example.com"]}}`), } invalidPayload := &signature.Payload{ ContentType: "invalid", Content: []byte(`invalid`), } tests := []struct { name string payload *signature.Payload wantErr bool }{ { name: "valid payload", payload: validPayload, wantErr: false, }, { name: "invalid content type", payload: invalidPayload, wantErr: true, }, { name: "nil payload", payload: nil, wantErr: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { _, err := DescriptorFromSignaturePayload(tt.payload) if (err != nil) != tt.wantErr { t.Errorf("DescriptorFromSignaturePayload() error = %v, wantErr %v", err, tt.wantErr) } }) } } notation-1.2.0/internal/httputil/000077500000000000000000000000001466375446100170565ustar00rootroot00000000000000notation-1.2.0/internal/httputil/client.go000066400000000000000000000041341466375446100206650ustar00rootroot00000000000000// Copyright The Notary Project Authors. // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package httputil import ( "context" "net/http" "github.com/notaryproject/notation/internal/trace" "github.com/notaryproject/notation/internal/version" "oras.land/oras-go/v2/registry/remote/auth" ) var userAgent = "notation/" + version.GetVersion() // NewAuthClient returns an *auth.Client with debug log and user agent set func NewAuthClient(ctx context.Context, httpClient *http.Client) *auth.Client { httpClient = trace.SetHTTPDebugLog(ctx, httpClient) client := &auth.Client{ Client: httpClient, Cache: auth.NewCache(), ClientID: "notation", } client.SetUserAgent(userAgent) return client } // NewClient returns an *http.Client with debug log and user agent set func NewClient(ctx context.Context, client *http.Client) *http.Client { client = trace.SetHTTPDebugLog(ctx, client) return SetUserAgent(client) } type userAgentTransport struct { base http.RoundTripper } // RoundTrip returns t.Base.RoundTrip with user agent set in the request Header func (t *userAgentTransport) RoundTrip(req *http.Request) (*http.Response, error) { r := req.Clone(req.Context()) if r.Header == nil { r.Header = http.Header{} } r.Header.Set("User-Agent", userAgent) return t.base.RoundTrip(r) } // SetUserAgent sets the user agent for all out-going requests. func SetUserAgent(client *http.Client) *http.Client { if client == nil { client = &http.Client{} } if client.Transport == nil { client.Transport = http.DefaultTransport } client.Transport = &userAgentTransport{ base: client.Transport, } return client } notation-1.2.0/internal/ioutil/000077500000000000000000000000001466375446100165065ustar00rootroot00000000000000notation-1.2.0/internal/ioutil/print.go000066400000000000000000000050211466375446100201670ustar00rootroot00000000000000// Copyright The Notary Project Authors. // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package ioutil import ( "encoding/json" "fmt" "io" "path/filepath" "text/tabwriter" "github.com/notaryproject/notation-go/config" ) func newTabWriter(w io.Writer) *tabwriter.Writer { return tabwriter.NewWriter(w, 0, 0, 3, ' ', 0) } // PrintKeyMap prints out key information given array of KeySuite func PrintKeyMap(w io.Writer, target *string, v []config.KeySuite) error { tw := newTabWriter(w) fmt.Fprintln(tw, "NAME\tKEY PATH\tCERTIFICATE PATH\tID\tPLUGIN NAME\t") for _, key := range v { name := key.Name if target != nil && key.Name == *target { name = "* " + name } kp := key.X509KeyPair if kp == nil { kp = &config.X509KeyPair{} } ext := key.ExternalKey if ext == nil { ext = &config.ExternalKey{} } fmt.Fprintf(tw, "%s\t%s\t%s\t%s\t%s\t\n", name, kp.KeyPath, kp.CertificatePath, ext.ID, ext.PluginName) } return tw.Flush() } // PrintMetadataMap prints out metadata given the metatdata map func PrintMetadataMap(w io.Writer, metadata map[string]string) error { tw := newTabWriter(w) fmt.Fprintln(tw, "\nKEY\tVALUE\t") for k, v := range metadata { fmt.Fprintf(tw, "%v\t%v\t\n", k, v) } return tw.Flush() } // PrintCertMap lists certificate files in the trust store given array of cert // paths func PrintCertMap(w io.Writer, certPaths []string) error { if len(certPaths) == 0 { return nil } tw := newTabWriter(w) fmt.Fprintln(tw, "STORE TYPE\tSTORE NAME\tCERTIFICATE\t") for _, cert := range certPaths { fileName := filepath.Base(cert) dir := filepath.Dir(cert) namedStore := filepath.Base(dir) dir = filepath.Dir(dir) storeType := filepath.Base(dir) fmt.Fprintf(tw, "%s\t%s\t%s\t\n", storeType, namedStore, fileName) } return tw.Flush() } // PrintObjectAsJSON takes an interface and prints it as an indented JSON string func PrintObjectAsJSON(i interface{}) error { jsonBytes, err := json.MarshalIndent(i, "", " ") if err != nil { return err } fmt.Println(string(jsonBytes)) return nil } notation-1.2.0/internal/osutil/000077500000000000000000000000001466375446100165205ustar00rootroot00000000000000notation-1.2.0/internal/osutil/file.go000066400000000000000000000074721466375446100200000ustar00rootroot00000000000000// Copyright The Notary Project Authors. // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package osutil import ( "crypto/sha256" "encoding/hex" "fmt" "io" "io/fs" "net/http" "os" "path/filepath" "strings" ) // MaxFileBytes is the maximum file bytes. // When used, the value should be strictly less than this number. var MaxFileBytes int64 = 256 * 1024 * 1024 // 256 MiB // WriteFile writes to a path with all parent directories created. func WriteFile(path string, data []byte) error { if err := os.MkdirAll(filepath.Dir(path), 0700); err != nil { return err } return os.WriteFile(path, data, 0600) } // WriteFileWithPermission writes to a path with all parent directories created. func WriteFileWithPermission(path string, data []byte, perm fs.FileMode, overwrite bool) error { if err := os.MkdirAll(filepath.Dir(path), 0700); err != nil { return err } flag := os.O_WRONLY | os.O_CREATE if overwrite { flag |= os.O_TRUNC } else { flag |= os.O_EXCL } file, err := os.OpenFile(path, flag, perm) if err != nil { return err } _, err = file.Write(data) if err != nil { file.Close() return err } return file.Close() } // CopyToDir copies the src file to dst. Existing file will be overwritten. func CopyToDir(src, dst string) (int64, error) { sourceFileStat, err := os.Stat(src) if err != nil { return 0, err } if !sourceFileStat.Mode().IsRegular() { return 0, fmt.Errorf("%s is not a regular file", src) } source, err := os.Open(src) if err != nil { return 0, err } defer source.Close() if err := os.MkdirAll(dst, 0700); err != nil { return 0, err } dstFile := filepath.Join(dst, filepath.Base(src)) destination, err := os.Create(dstFile) if err != nil { return 0, err } defer destination.Close() err = destination.Chmod(0600) if err != nil { return 0, err } return io.Copy(destination, source) } // IsRegularFile checks if path is a regular file func IsRegularFile(path string) (bool, error) { fileStat, err := os.Stat(path) if err != nil { return false, err } return fileStat.Mode().IsRegular(), nil } // CopyFromReaderToDir copies file from src to dst where dst is the destination // file path. func CopyFromReaderToDir(src io.Reader, dst string, perm fs.FileMode) error { dstFile, err := os.Create(dst) if err != nil { return err } if _, err := io.Copy(dstFile, src); err != nil { dstFile.Close() return err } if err := dstFile.Chmod(perm); err != nil { dstFile.Close() return err } return dstFile.Close() } // DetectFileType returns a file's content type given path func DetectFileType(path string) (string, error) { rc, err := os.Open(path) if err != nil { return "", err } defer rc.Close() lr := io.LimitReader(rc, 512) header, err := io.ReadAll(lr) if err != nil { return "", err } return http.DetectContentType(header), nil } // ValidateSHA256Sum returns nil if SHA256 of file at path equals to checksum. func ValidateSHA256Sum(path string, checksum string) error { rc, err := os.Open(path) if err != nil { return err } defer rc.Close() sha256Hash := sha256.New() if _, err := io.Copy(sha256Hash, rc); err != nil { return err } sha256sum := sha256Hash.Sum(nil) enc := hex.EncodeToString(sha256sum[:]) if !strings.EqualFold(enc, checksum) { return fmt.Errorf("plugin SHA-256 checksum does not match user input. Expecting %s", checksum) } return nil } notation-1.2.0/internal/osutil/file_test.go000066400000000000000000000163501466375446100210320ustar00rootroot00000000000000// Copyright The Notary Project Authors. // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package osutil import ( "bytes" "os" "path/filepath" "runtime" "testing" ) func validFileContent(t *testing.T, filename string, content []byte) { b, err := os.ReadFile(filename) if err != nil { t.Fatal(err) } if !bytes.Equal(content, b) { t.Fatal("file content is not correct") } } func TestWriteFile(t *testing.T) { t.Run("write file", func(t *testing.T) { tempDir := t.TempDir() data := []byte("data") filename := filepath.Join(tempDir, "a", "file.txt") if err := WriteFile(filename, data); err != nil { t.Fatal(err) } }) t.Run("write file with directory permission error", func(t *testing.T) { if runtime.GOOS == "windows" { t.Skip("skipping test on Windows") } tempDir := t.TempDir() data := []byte("data") // forbid writing to tempDir if err := os.Chmod(tempDir, 0000); err != nil { t.Fatal(err) } filename := filepath.Join(tempDir, "a", "file.txt") if err := WriteFile(filename, data); err == nil { t.Fatal("should write failed") } }) t.Run("check file correctness", func(t *testing.T) { tempDir := t.TempDir() data := []byte("data") filename := filepath.Join(tempDir, "a", "file.txt") if err := WriteFile(filename, data); err != nil { t.Fatal(err) } validFileContent(t, filename, data) }) } func TestWriteFileWithPermission(t *testing.T) { t.Run("write without override", func(t *testing.T) { tempDir := t.TempDir() data := []byte("data") filename := filepath.Join(tempDir, "file.txt") if err := WriteFileWithPermission(filename, data, 0644, false); err != nil { t.Fatal(err) } if err := WriteFileWithPermission(filename, data, 0644, false); err == nil { t.Fatal("should have an error") } }) t.Run("write with override", func(t *testing.T) { tempDir := t.TempDir() data := []byte("data") filename := filepath.Join(tempDir, "file.txt") if err := WriteFileWithPermission(filename, data, 0644, false); err != nil { t.Fatal(err) } if err := WriteFileWithPermission(filename, data, 0644, true); err != nil { t.Fatal(err) } }) t.Run("write with directory permission error", func(t *testing.T) { if runtime.GOOS == "windows" { t.Skip("skipping test on Windows") } tempDir := t.TempDir() data := []byte("data") filename := filepath.Join(tempDir, "a", "file.txt") // forbid writing to tempDir if err := os.Chmod(tempDir, 0000); err != nil { t.Fatal(err) } if err := WriteFileWithPermission(filename, data, 0644, false); err == nil { t.Fatal("should have an error") } }) t.Run("valid file content", func(t *testing.T) { tempDir := t.TempDir() data := []byte("data") filename := filepath.Join(tempDir, "file.txt") if err := WriteFileWithPermission(filename, data, 0644, false); err != nil { t.Fatal(err) } if err := WriteFileWithPermission(filename, data, 0644, false); err == nil { t.Fatal("should have an error") } validFileContent(t, filename, data) }) } func TestCopyToDir(t *testing.T) { t.Run("copy file", func(t *testing.T) { tempDir := t.TempDir() data := []byte("data") filename := filepath.Join(tempDir, "a", "file.txt") if err := WriteFile(filename, data); err != nil { t.Fatal(err) } destDir := filepath.Join(tempDir, "b") if _, err := CopyToDir(filename, destDir); err != nil { t.Fatal(err) } }) t.Run("source directory permission error", func(t *testing.T) { if runtime.GOOS == "windows" { t.Skip("skipping test on Windows") } tempDir := t.TempDir() destDir := t.TempDir() data := []byte("data") filename := filepath.Join(tempDir, "a", "file.txt") if err := WriteFile(filename, data); err != nil { t.Fatal(err) } if err := os.Chmod(tempDir, 0000); err != nil { t.Fatal(err) } defer os.Chmod(tempDir, 0700) if _, err := CopyToDir(filename, destDir); err == nil { t.Fatal("should have error") } }) t.Run("not a regular file", func(t *testing.T) { tempDir := t.TempDir() destDir := t.TempDir() if _, err := CopyToDir(tempDir, destDir); err == nil { t.Fatal("should have error") } }) t.Run("source file permission error", func(t *testing.T) { if runtime.GOOS == "windows" { t.Skip("skipping test on Windows") } tempDir := t.TempDir() destDir := t.TempDir() data := []byte("data") // prepare file filename := filepath.Join(tempDir, "a", "file.txt") if err := WriteFile(filename, data); err != nil { t.Fatal(err) } // forbid reading if err := os.Chmod(filename, 0000); err != nil { t.Fatal(err) } defer os.Chmod(filename, 0600) if _, err := CopyToDir(filename, destDir); err == nil { t.Fatal("should have error") } }) t.Run("dest directory permission error", func(t *testing.T) { if runtime.GOOS == "windows" { t.Skip("skipping test on Windows") } tempDir := t.TempDir() destTempDir := t.TempDir() data := []byte("data") // prepare file filename := filepath.Join(tempDir, "a", "file.txt") if err := WriteFile(filename, data); err != nil { t.Fatal(err) } // forbid dest directory operation if err := os.Chmod(destTempDir, 0000); err != nil { t.Fatal(err) } defer os.Chmod(destTempDir, 0700) if _, err := CopyToDir(filename, filepath.Join(destTempDir, "a")); err == nil { t.Fatal("should have error") } }) t.Run("dest directory permission error 2", func(t *testing.T) { if runtime.GOOS == "windows" { t.Skip("skipping test on Windows") } tempDir := t.TempDir() destTempDir := t.TempDir() data := []byte("data") // prepare file filename := filepath.Join(tempDir, "a", "file.txt") if err := WriteFile(filename, data); err != nil { t.Fatal(err) } // forbid writing to destTempDir if err := os.Chmod(destTempDir, 0000); err != nil { t.Fatal(err) } defer os.Chmod(destTempDir, 0700) if _, err := CopyToDir(filename, destTempDir); err == nil { t.Fatal("should have error") } }) t.Run("copy file and check content", func(t *testing.T) { tempDir := t.TempDir() data := []byte("data") filename := filepath.Join(tempDir, "a", "file.txt") if err := WriteFile(filename, data); err != nil { t.Fatal(err) } destDir := filepath.Join(tempDir, "b") if _, err := CopyToDir(filename, destDir); err != nil { t.Fatal(err) } validFileContent(t, filepath.Join(destDir, "file.txt"), data) }) } func TestValidateChecksum(t *testing.T) { expectedErrorMsg := "plugin SHA-256 checksum does not match user input. Expecting abcd123" if err := ValidateSHA256Sum("./testdata/test", "abcd123"); err == nil || err.Error() != expectedErrorMsg { t.Fatalf("expected err %s, but got %v", expectedErrorMsg, err) } if err := ValidateSHA256Sum("./testdata/test", "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"); err != nil { t.Fatalf("expected nil err, but got %v", err) } } notation-1.2.0/internal/osutil/testdata/000077500000000000000000000000001466375446100203315ustar00rootroot00000000000000notation-1.2.0/internal/osutil/testdata/test000066400000000000000000000000001466375446100212210ustar00rootroot00000000000000notation-1.2.0/internal/slices/000077500000000000000000000000001466375446100164635ustar00rootroot00000000000000notation-1.2.0/internal/slices/slices.go000066400000000000000000000014141466375446100202740ustar00rootroot00000000000000// Copyright The Notary Project Authors. // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package slices // Contains reports whether v is present in s. func Contains[E comparable](s []E, v E) bool { for _, vs := range s { if v == vs { return true } } return false } notation-1.2.0/internal/slices/slices_test.go000066400000000000000000000021301466375446100213270ustar00rootroot00000000000000// Copyright The Notary Project Authors. // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package slices import ( "testing" ) func TestContainerElement(t *testing.T) { tests := []struct { c []string v string want bool }{ {nil, "", false}, {[]string{}, "", false}, {[]string{"1", "2", "3"}, "4", false}, {[]string{"1", "2", "3"}, "2", true}, {[]string{"1", "2", "2", "3"}, "2", true}, {[]string{"1", "2", "3", "2"}, "2", true}, } for _, tt := range tests { if got := Contains(tt.c, tt.v); got != tt.want { t.Errorf("ContainerElement() = %v, want %v", got, tt.want) } } } notation-1.2.0/internal/testdata/000077500000000000000000000000001466375446100170125ustar00rootroot00000000000000notation-1.2.0/internal/testdata/CertChain.pem000066400000000000000000000023221466375446100213540ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIIBljCCATugAwIBAgIQJOyDt70f+HOyQMEt06yvpjAKBggqhkjOPQQDAjAkMRAw DgYDVQQKEwdBY21lIENvMRAwDgYDVQQDEwdSb290IENBMB4XDTIyMDcyMjA1MjEz N1oXDTIzMDcyMjA1MjEzN1owKDEQMA4GA1UEChMHQWNtZSBDbzEUMBIGA1UEAwwL dGVzdF9jZXJ0XzEwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAATnf3lSRtUYOeph UQZvUm5niB8kpm7kn6iAm2zwCTBeqKbUtgESCbN+x6TTpWZIaEo+CDu1rPUdicB3 FUwXNzz8o0swSTAOBgNVHQ8BAf8EBAMCBaAwEwYDVR0lBAwwCgYIKwYBBQUHAwEw DAYDVR0TAQH/BAIwADAUBgNVHREEDTALgglsb2NhbGhvc3QwCgYIKoZIzj0EAwID SQAwRgIhAMgdV/zJnwK0J4ZBXZVwAB6abpgNcESFScDeQQyIzRs8AiEAjjLTfkXp CuoXnu5/hYy6Li7Smw3UbW3XKkekOELMFYo= -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIIBnjCCAUOgAwIBAgIQBUvhbMcjM35qmJzncyZ5tzAKBggqhkjOPQQDAjAkMRAw DgYDVQQKEwdBY21lIENvMRAwDgYDVQQDEwdSb290IENBMB4XDTIyMDcyMjA1MjEz N1oXDTIzMDcyMjA1MjEzN1owJDEQMA4GA1UEChMHQWNtZSBDbzEQMA4GA1UEAxMH Um9vdCBDQTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABGmby5GUiBDR+Ge+s/R9 EqOfoDwEdDBPYU0emJg8j8CPJGM0ldalI1Sk7YMTIi34clvfTqEixE7nDwQj8FjQ VvCjVzBVMA4GA1UdDwEB/wQEAwICBDATBgNVHSUEDDAKBggrBgEFBQcDATAPBgNV HRMBAf8EBTADAQH/MB0GA1UdDgQWBBTcOHZMx0z3I9Hi8oa2Kp0umdXOsTAKBggq hkjOPQQDAgNJADBGAiEArHTaO3f6vaiI+4IOrR7SYSzeHIAqoFAWFcf1yOzxDA4C IQDRcDIPWJd7pXvFJT/Q++Vkq9QuUhqrigCQDkgksnxf5w== -----END CERTIFICATE----- notation-1.2.0/internal/testdata/Empty.txt000066400000000000000000000000001466375446100206370ustar00rootroot00000000000000notation-1.2.0/internal/testdata/GlobalSign.der000066400000000000000000000015431466375446100215320ustar00rootroot000000000000000_0G !XS0  *H  0L1 0U GlobalSign Root CA - R310U  GlobalSign10U GlobalSign0 090318100000Z 290318100000Z0L1 0U GlobalSign Root CA - R310U  GlobalSign10U GlobalSign0"0  *H 0 %vyx"(vŭrFCDz_$.K`FR Gpld,= +׶y;wIjb/^h߉'8>&Y sް&[`I(i;(坊aW7tt:r/.л=3+S:sA :O.2`W˹hh8&`uw I@H1a^wdz_b lTin郓qviB0@0U0U00UK.E$MPc0  *H  K@P TEI A(3kt- sgJD{xnlo)39EÎWlS-$lcShgV>5!hS̐]FzX(/7ADmS(~g׊L'Lssvz- ,pUA2s*n|!LԼu]xf:1D3@ZI橠gݤ'O9X$\Fdivv=Y]BvizHftKc:=E%D+~am3K}Ï!Ռp,A`cDvb~d3щίCw !T)%lRQGt&Auz_?ɼA[P1r" |Lu?c!_ QkoOE_ ~ &i/-٩B0@0U00U0Uq]dL.g?纘O0  *H  a}lđádhVwpJx\ _)V 6I]Dcଡ଼f# =ymkTY9"SD]Pz}b! sfѠ`_襴m5|Z֢8xM Gr 20Y.qVjoPmhz6z$ Pz#aB)͢ Aќd&LPAq=?Mp# J܁2  Ok t094!U2qI(PMMuACDO,6E#SlogUFL?n(Zy&ҤbJGJ gf~[A;;cTQ*xίI󒙶a҅POBl C:qM&5]b2Ҡ+TWJ'S趉m[h#QV𦀠Su)wތ!G=uf~notation-1.2.0/internal/trace/000077500000000000000000000000001466375446100162775ustar00rootroot00000000000000notation-1.2.0/internal/trace/context.go000066400000000000000000000034171466375446100203170ustar00rootroot00000000000000// Copyright The Notary Project Authors. // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // Copied and adapted from oras (https://github.com/oras-project/oras) /* Copyright The ORAS Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package trace import ( "context" "github.com/notaryproject/notation-go/log" "github.com/sirupsen/logrus" ) // WithLoggerLevel returns a context with logrus log entry. func WithLoggerLevel(ctx context.Context, level logrus.Level) context.Context { // set formatter var formatter logrus.TextFormatter if level == logrus.DebugLevel { formatter.FullTimestamp = true } else { formatter.DisableTimestamp = true } // create logger logger := logrus.New() logger.SetFormatter(&formatter) logger.SetLevel(level) // save logger to context return log.WithLogger(ctx, logger) } notation-1.2.0/internal/trace/context_test.go000066400000000000000000000041651466375446100213570ustar00rootroot00000000000000// Copyright The Notary Project Authors. // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // Copied and adapted from oras (https://github.com/oras-project/oras) /* Copyright The ORAS Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package trace import ( "context" "testing" "github.com/notaryproject/notation-go/log" "github.com/sirupsen/logrus" ) func TestWithLoggerLevel(t *testing.T) { t.Run("debug level log", func(t *testing.T) { ctx := WithLoggerLevel(context.Background(), logrus.DebugLevel) logger := log.GetLogger(ctx) if logrusLogger, ok := logger.(*logrus.Logger); ok { if logrusLogger.Level != logrus.DebugLevel { t.Errorf("log level want = %v, got %v", logrus.DebugLevel, logrusLogger.Level) } } else { t.Fatal("should log with logrus") } }) t.Run("info level log", func(t *testing.T) { ctx := WithLoggerLevel(context.Background(), logrus.InfoLevel) logger := log.GetLogger(ctx) if logrusLogger, ok := logger.(*logrus.Logger); ok { if logrusLogger.Level != logrus.InfoLevel { t.Errorf("log level want = %v, got %v", logrus.InfoLevel, logrusLogger.Level) } } else { t.Fatal("should log with logrus") } }) } notation-1.2.0/internal/trace/transport.go000066400000000000000000000060461466375446100206700ustar00rootroot00000000000000// Copyright The Notary Project Authors. // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // Copied and adapted from oras (https://github.com/oras-project/oras) /* Copyright The ORAS Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package trace import ( "context" "net/http" "strings" "github.com/notaryproject/notation-go/log" "github.com/sirupsen/logrus" ) // Transport is an http.RoundTripper that keeps track of the in-flight // request and add hooks to report HTTP tracing events. type Transport struct { http.RoundTripper } func NewTransport(base http.RoundTripper) *Transport { return &Transport{base} } // RoundTrip calls base roundtrip while keeping track of the current request. func (t *Transport) RoundTrip(req *http.Request) (resp *http.Response, err error) { ctx := req.Context() e := log.GetLogger(ctx) e.Debugf("> Request: %q %q", req.Method, req.URL) e.Debugf("> Request headers:") logHeader(req.Header, e) resp, err = t.RoundTripper.RoundTrip(req) if err != nil { e.Errorf("Error in getting response: %w", err) } else if resp == nil { e.Errorf("No response obtained for request %s %q", req.Method, req.URL) } else { e.Debugf("< Response status: %q", resp.Status) e.Debugf("< Response headers:") logHeader(resp.Header, e) } return resp, err } // logHeader prints out the provided header keys and values, with auth header // scrubbed. func logHeader(header http.Header, e log.Logger) { if len(header) > 0 { for k, v := range header { if strings.EqualFold(k, "Authorization") { v = []string{"*****"} } e.Debugf(" %q: %q", k, strings.Join(v, ", ")) } } else { e.Debugf(" Empty header") } } // SetHTTPDebugLog sets up http debug log with logrus.Logger func SetHTTPDebugLog(ctx context.Context, client *http.Client) *http.Client { if logrusLog, ok := log.GetLogger(ctx).(*logrus.Logger); !ok || logrusLog.Level != logrus.DebugLevel { return client } if client == nil { client = &http.Client{} } if client.Transport == nil { client.Transport = http.DefaultTransport } client.Transport = NewTransport(client.Transport) return client } notation-1.2.0/internal/trace/transport_test.go000066400000000000000000000052001466375446100217160ustar00rootroot00000000000000// Copyright The Notary Project Authors. // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // Copied and adapted from oras (https://github.com/oras-project/oras) /* Copyright The ORAS Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package trace import ( "errors" "net/http" "testing" ) type TransportMock struct { resp *http.Response respErr error } // RoundTrip calls base roundtrip while keeping track of the current request. func (t *TransportMock) RoundTrip(req *http.Request) (resp *http.Response, err error) { resp = t.resp err = t.respErr return } func TestTransport_RoundTrip(t *testing.T) { t.Run("nil response", func(t *testing.T) { req := &http.Request{} transport := NewTransport(&TransportMock{}) _, err := transport.RoundTrip(req) if err != nil { t.Fatal("should have no error") } }) t.Run("error response", func(t *testing.T) { errMsg := "response error" req := &http.Request{} transport := NewTransport(&TransportMock{respErr: errors.New(errMsg)}) _, err := transport.RoundTrip(req) if err.Error() != errMsg { t.Fatalf("want err = %s, got %s", errMsg, err) } }) t.Run("valid response", func(t *testing.T) { req := &http.Request{} transport := NewTransport(&TransportMock{resp: &http.Response{}}) _, err := transport.RoundTrip(req) if err != nil { t.Fatal("should have no error") } }) t.Run("request header with Authorization", func(t *testing.T) { header := http.Header{} header.Add("Authorization", "abc") req := &http.Request{Header: header} transport := NewTransport(&TransportMock{resp: &http.Response{}}) _, err := transport.RoundTrip(req) if err != nil { t.Fatal("should have no error") } }) } notation-1.2.0/internal/tree/000077500000000000000000000000001466375446100161405ustar00rootroot00000000000000notation-1.2.0/internal/tree/tree.go000066400000000000000000000035061466375446100174320ustar00rootroot00000000000000// Copyright The Notary Project Authors. // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package tree import ( "fmt" ) const ( treeItemPrefix = "├── " treeItemPrefixLast = "└── " subTreePrefix = "│ " subTreePrefixLast = " " ) // represents a Node in a tree type Node struct { Value string Children []*Node } // creates a new Node with the given value func New(value string) *Node { return &Node{Value: value} } // adds a new child node with the given value func (parent *Node) Add(value string) *Node { node := New(value) parent.Children = append(parent.Children, node) return node } // adds a new child node with the formatted pair as the value func (parent *Node) AddPair(key string, value string) *Node { return parent.Add(key + ": " + value) } // prints the tree represented by the root node func (root *Node) Print() { print("", "", "", root) } func print(prefix string, itemMarker string, nextPrefix string, n *Node) { fmt.Println(prefix + itemMarker + n.Value) nextItemPrefix := treeItemPrefix nextSubTreePrefix := subTreePrefix if len(n.Children) > 0 { for i, child := range n.Children { if i == len(n.Children)-1 { nextItemPrefix = treeItemPrefixLast nextSubTreePrefix = subTreePrefixLast } print(nextPrefix, nextItemPrefix, nextPrefix+nextSubTreePrefix, child) } } } notation-1.2.0/internal/tree/tree_test.go000066400000000000000000000040231466375446100204640ustar00rootroot00000000000000// Copyright The Notary Project Authors. // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package tree import ( "reflect" "testing" ) func TestNodeCreation(t *testing.T) { node := New("root") expected := Node{Value: "root"} if !reflect.DeepEqual(*node, expected) { t.Fatalf("expected %+v, got %+v", expected, *node) } } func TestNodeAdd(t *testing.T) { root := New("root") root.Add("child") if !root.ContainsChild("child") { t.Error("expected root to have child node with value 'child'") t.Fatalf("actual root: %+v", root) } } func TestNodeAddPair(t *testing.T) { root := New("root") root.AddPair("key", "value") if !root.ContainsChild("key: value") { t.Error("expected root to have child node with value 'key: value'") t.Fatalf("actual root: %+v", root) } } func ExampleRootPrint() { root := New("root") root.Print() // Output: // root } func ExampleSingleLayerPrint() { root := New("root") root.Add("child1") root.Add("child2") root.Print() // Output: // root // ├── child1 // └── child2 } func ExampleMultiLayerPrint() { root := New("root") child1 := root.Add("child1") child1.AddPair("key", "value") child2 := root.Add("child2") child2.Add("child2.1") child2.Add("child2.2") root.Print() // Output: // root // ├── child1 // │ └── key: value // └── child2 // ├── child2.1 // └── child2.2 } func (n *Node) ContainsChild(value string) bool { for _, child := range n.Children { if child.Value == value { return true } } return false } notation-1.2.0/internal/version/000077500000000000000000000000001466375446100166665ustar00rootroot00000000000000notation-1.2.0/internal/version/version.go000066400000000000000000000022431466375446100207030ustar00rootroot00000000000000// Copyright The Notary Project Authors. // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package version var ( // Version shows the current notation version, optionally with pre-release. Version = "v1.2.0" // BuildMetadata stores the build metadata. // // When execute `make build` command, it will be overridden by // environment variable `BUILD_METADATA`. If commit tag was set, // BuildMetadata will be empty. BuildMetadata = "unreleased" // GitCommit stores the git HEAD commit id GitCommit = "" ) // GetVersion returns the version string in SemVer 2. func GetVersion() string { if BuildMetadata == "" { return Version } return Version + "+" + BuildMetadata } notation-1.2.0/internal/version/version_test.go000066400000000000000000000021241466375446100217400ustar00rootroot00000000000000// Copyright The Notary Project Authors. // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package version import "testing" func TestGetVersion(t *testing.T) { t.Run("BuildMetadata is empty", func(t *testing.T) { Version = "1.0" BuildMetadata = "" v := GetVersion() if Version != v { t.Errorf("Should return Version = %s, got %s", Version, v) } }) t.Run("BuildMetadata is not empty", func(t *testing.T) { Version = "1.0" BuildMetadata = "unreleased" v := GetVersion() want := "1.0+unreleased" if want != v { t.Errorf("Should return Version = %s, got %s", want, v) } }) } notation-1.2.0/internal/x509/000077500000000000000000000000001466375446100157065ustar00rootroot00000000000000notation-1.2.0/internal/x509/cert.go000066400000000000000000000017261466375446100172000ustar00rootroot00000000000000// Copyright The Notary Project Authors. // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package x509 import ( "bytes" "crypto/x509" ) // IsRootCertificate returns true if cert is a root certificate. // A root certificate MUST be a self-signed and self-issued certificate. func IsRootCertificate(cert *x509.Certificate) (bool, error) { if err := cert.CheckSignatureFrom(cert); err != nil { return false, err } return bytes.Equal(cert.RawSubject, cert.RawIssuer), nil } notation-1.2.0/internal/x509/cert_test.go000066400000000000000000000040361466375446100202340ustar00rootroot00000000000000// Copyright The Notary Project Authors. // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package x509 import ( "testing" corex509 "github.com/notaryproject/notation-core-go/x509" ) func TestIsRootCertificate(t *testing.T) { tsaRoot, err := corex509.ReadCertificateFile("../testdata/tsaRootCA.cer") if err != nil { t.Fatal(err) } isRoot, err := IsRootCertificate(tsaRoot[0]) if err != nil { t.Fatal(err) } if !isRoot { t.Fatal("expected IsRootCertificate to return true") } intermediate, err := corex509.ReadCertificateFile("../testdata/intermediate.pem") if err != nil { t.Fatal(err) } expectedErrMsg := "crypto/rsa: verification error" _, err = IsRootCertificate(intermediate[0]) if err == nil || err.Error() != expectedErrMsg { t.Fatalf("expected %s, but got %s", expectedErrMsg, err) } selfSigned, err := corex509.ReadCertificateFile("../testdata/self-signed.crt") if err != nil { t.Fatal(err) } expectedErrMsg = "x509: invalid signature: parent certificate cannot sign this kind of certificate" _, err = IsRootCertificate(selfSigned[0]) if err == nil || err.Error() != expectedErrMsg { t.Fatalf("expected %s, but got %s", expectedErrMsg, err) } notSelfIssued, err := corex509.ReadCertificateFile("../testdata/notSelfIssued.crt") if err != nil { t.Fatal(err) } expectedErrMsg = "x509: invalid signature: parent certificate cannot sign this kind of certificate" isRoot, err = IsRootCertificate(notSelfIssued[0]) if err != nil { t.Fatal(err) } if isRoot { t.Fatal("expected IsRootCertificate to return false") } } notation-1.2.0/pkg/000077500000000000000000000000001466375446100141465ustar00rootroot00000000000000notation-1.2.0/pkg/configutil/000077500000000000000000000000001466375446100163115ustar00rootroot00000000000000notation-1.2.0/pkg/configutil/once.go000066400000000000000000000027171466375446100175730ustar00rootroot00000000000000// Copyright The Notary Project Authors. // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package configutil import ( "strings" "sync" "github.com/notaryproject/notation-go/config" "github.com/notaryproject/notation/internal/envelope" ) var ( // configInfo is the config.json data configInfo *config.Config configOnce sync.Once ) // LoadConfigOnce returns the previously read config file. // If previous config file does not exist, it reads the config from file // or return a default config if not found. // The returned config is only suitable for read only scenarios for short-lived processes. func LoadConfigOnce() (*config.Config, error) { var err error configOnce.Do(func() { configInfo, err = config.LoadConfig() if err != nil { return } // set default value configInfo.SignatureFormat = strings.ToLower(configInfo.SignatureFormat) if configInfo.SignatureFormat == "" { configInfo.SignatureFormat = envelope.JWS } }) return configInfo, err } notation-1.2.0/pkg/configutil/once_test.go000066400000000000000000000016361466375446100206310ustar00rootroot00000000000000// Copyright The Notary Project Authors. // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package configutil import ( "testing" ) func TestLoadConfigOnce(t *testing.T) { config1, err := LoadConfigOnce() if err != nil { t.Fatal("LoadConfigOnce failed.") } config2, err := LoadConfigOnce() if err != nil { t.Fatal("LoadConfigOnce failed.") } if config1 != config2 { t.Fatal("LoadConfigOnce is invalid.") } } notation-1.2.0/pkg/configutil/testdata/000077500000000000000000000000001466375446100201225ustar00rootroot00000000000000notation-1.2.0/pkg/configutil/testdata/config.json000066400000000000000000000000511466375446100222560ustar00rootroot00000000000000{ "insecureRegistries": ["reg1.io"] }notation-1.2.0/pkg/configutil/testdata/no_default_key_signingkeys/000077500000000000000000000000001466375446100255245ustar00rootroot00000000000000notation-1.2.0/pkg/configutil/testdata/no_default_key_signingkeys/signingkeys.json000066400000000000000000000002601466375446100307470ustar00rootroot00000000000000{ "keys": [ { "name": "e2e", "keyPath": "notation/localkeys/e2e.key", "certPath": "notation/localkeys/e2e.crt" } ] }notation-1.2.0/pkg/configutil/testdata/valid_signingkeys/000077500000000000000000000000001466375446100236335ustar00rootroot00000000000000notation-1.2.0/pkg/configutil/testdata/valid_signingkeys/signingkeys.json000066400000000000000000000003061466375446100270570ustar00rootroot00000000000000{ "default": "e2e", "keys": [ { "name": "e2e", "keyPath": "notation/localkeys/e2e.key", "certPath": "notation/localkeys/e2e.crt" } ] }notation-1.2.0/pkg/configutil/util.go000066400000000000000000000030071466375446100176150ustar00rootroot00000000000000// Copyright The Notary Project Authors. // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package configutil import ( "errors" "strings" "github.com/notaryproject/notation-go/config" ) var ( // ErrKeyNotFound indicates that the signing key is not found. ErrKeyNotFound = errors.New("signing key not found") ) // IsRegistryInsecure checks whether the registry is in the list of insecure registries. func IsRegistryInsecure(target string) bool { config, err := LoadConfigOnce() if err != nil { return false } for _, registry := range config.InsecureRegistries { if strings.EqualFold(registry, target) { return true } } return false } // ResolveKey resolves the key by name. // The default key is attempted if name is empty. func ResolveKey(name string) (config.KeySuite, error) { signingKeys, err := config.LoadSigningKeys() if err != nil { return config.KeySuite{}, err } // if name is empty, look for default signing key if name == "" { return signingKeys.GetDefault() } return signingKeys.Get(name) } notation-1.2.0/pkg/configutil/util_test.go000066400000000000000000000077301466375446100206630ustar00rootroot00000000000000// Copyright The Notary Project Authors. // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package configutil import ( "os" "path/filepath" "runtime" "strings" "sync" "testing" "github.com/notaryproject/notation-go/dir" ) func TestIsRegistryInsecure(t *testing.T) { configOnce = sync.Once{} // for restore dir defer func(oldDir string) { dir.UserConfigDir = oldDir configOnce = sync.Once{} }(dir.UserConfigDir) // update config dir dir.UserConfigDir = "testdata" type args struct { target string } tests := []struct { name string args args want bool }{ {name: "hit registry", args: args{target: "reg1.io"}, want: true}, {name: "miss registry", args: args{target: "reg2.io"}, want: false}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if got := IsRegistryInsecure(tt.args.target); got != tt.want { t.Errorf("IsRegistryInsecure() = %v, want %v", got, tt.want) } }) } } func TestIsRegistryInsecureMissingConfig(t *testing.T) { configOnce = sync.Once{} // for restore dir defer func(oldDir string) { dir.UserConfigDir = oldDir configOnce = sync.Once{} }(dir.UserConfigDir) // update config dir dir.UserConfigDir = "./testdata2" type args struct { target string } tests := []struct { name string args args want bool }{ {name: "missing config", args: args{target: "reg1.io"}, want: false}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if got := IsRegistryInsecure(tt.args.target); got != tt.want { t.Errorf("IsRegistryInsecure() = %v, want %v", got, tt.want) } }) } } func TestIsRegistryInsecureConfigPermissionError(t *testing.T) { if runtime.GOOS == "windows" { t.Skip("skipping test on Windows") } configDir := "./testdata" // for restore dir defer func(oldDir string) error { // restore permission dir.UserConfigDir = oldDir configOnce = sync.Once{} return os.Chmod(filepath.Join(configDir, "config.json"), 0644) }(dir.UserConfigDir) // update config dir dir.UserConfigDir = configDir // forbid reading the file if err := os.Chmod(filepath.Join(configDir, "config.json"), 0000); err != nil { t.Error(err) } if IsRegistryInsecure("reg1.io") { t.Error("should false because of missing config.json read permission.") } } func TestResolveKey(t *testing.T) { defer func(oldDir string) { dir.UserConfigDir = oldDir }(dir.UserConfigDir) t.Run("valid e2e key", func(t *testing.T) { dir.UserConfigDir = "./testdata/valid_signingkeys" keySuite, err := ResolveKey("e2e") if err != nil { t.Fatal(err) } if keySuite.Name != "e2e" { t.Error("key name is not correct.") } }) t.Run("key name is empty (using default key)", func(t *testing.T) { dir.UserConfigDir = "./testdata/valid_signingkeys" keySuite, err := ResolveKey("") if err != nil { t.Fatal(err) } if keySuite.Name != "e2e" { t.Error("key name is not correct.") } }) t.Run("signingkeys.json without read permission", func(t *testing.T) { if runtime.GOOS == "windows" { t.Skip("skipping test on Windows") } dir.UserConfigDir = "./testdata/valid_signingkeys" defer func() error { // restore the permission return os.Chmod(filepath.Join(dir.UserConfigDir, "signingkeys.json"), 0644) }() // forbid reading the file if err := os.Chmod(filepath.Join(dir.UserConfigDir, "signingkeys.json"), 0000); err != nil { t.Error(err) } _, err := ResolveKey("") if !strings.Contains(err.Error(), "permission denied") { t.Error("should error with permission denied") } }) } notation-1.2.0/specs/000077500000000000000000000000001466375446100145025ustar00rootroot00000000000000notation-1.2.0/specs/commandline/000077500000000000000000000000001466375446100167705ustar00rootroot00000000000000notation-1.2.0/specs/commandline/certificate.md000066400000000000000000000205541466375446100216020ustar00rootroot00000000000000# notation certificate ## Description Use ```notation certificate``` command to add/list/delete certificates in notation's trust store. Updating an existing certificate is not allowed since the thumbprint will be inconsistent, which results in a new certificate. The trust store is in the format of a directory in the filesystem as`x509///*.crt|*.cer|*.pem`. Currently two types of trust store are supported: * `Certificate Authority`: The directory name is `ca`. * `Signing Authority`: The directory name is `signingAuthority` There could be more trust store types introduced in the future. Here is an example of trust store directory structure: ```text $XDG_CONFIG_HOME/notation/truststore /x509 /ca /acme-rockets cert1.pem cert2.pem /sub-dir # sub directory is ignored cert-3.pem # certs under sub directory is ignored /signingAuthority /wabbit-networks cert3.crt ``` In this example, there are two certificates stored in trust store named `acme-rockets` of type `ca`. There is one certificate stored in trust store named `wabbit-networks` of type `signingAuthority`. ## Outline ### notation certificate ```text Manage certificates in trust store for signature verification. Usage: notation certificate [command] Aliases: certificate, cert Available Commands: add Add certificates to the trust store. delete Delete certificates from the trust store. generate-test Generate a test RSA key and a corresponding self-signed certificate. list List certificates in the trust store. show Show certificate details given trust store type, named store, and certificate file name. If the certificate file contains multiple certificates, then all certificates are displayed. Flags: -h, --help help for certificate ``` ### notation certificate add ```text Add certificates to the trust store. Usage: notation certificate add --type --store [flags] ... Flags: -h, --help help for add -s, --store string specify named store -t, --type string specify trust store type, options: ca, signingAuthority ``` ### notation certificate list ```text List certificates in the trust store. Usage: notation certificate list [flags] Aliases: list, ls Flags: -d, --debug debug mode -h, --help help for list -s, --store string specify named store -t, --type string specify trust store type, options: ca, signingAuthority -v, --verbose verbose mode ``` ### notation certificate show ```text Show certificate details of given trust store name, trust store type, and certificate file name. If the certificate file contains multiple certificates, then all certificates are displayed. Usage: notation certificate show --type --store [flags] Flags: -d, --debug debug mode -h, --help help for show -s, --store string specify named store -t, --type string specify trust store type, options: ca, signingAuthority -v, --verbose verbose mode ``` ### notation certificate delete ```text Delete certificates from the trust store. Usage: notation certificate delete --type --store [flags] (--all | ) Flags: -a, --all delete all certificates in the named store -h, --help help for delete -s, --store string specify named store -t, --type string specify trust store type, options: ca, signingAuthority -y, --yes do not prompt for confirmation ``` ### notation certificate generate-test ```text Generate a test RSA key and a corresponding self-signed certificate. Usage: notation certificate generate-test [flags] Flags: -b, --bits int RSA key bits (default 2048) --default mark as default signing key -h, --help help for generate-test ``` ## Usage ### Add certificates to the trust store ```bash notation certificate add --type --store ... ``` For each certificate in a certificate file, it MUST be either CA type or self-signed. Upon successful adding, the certificate files are added into directory`{NOTATION_CONFIG}/truststore/x509///`, and a list of certificate filepaths are printed out. If the adding fails, an error message is printed out by listing which certificate files are successfully added, and which certificate files are not along with detailed reasons. ### List all certificate files stored in the trust store ```bash notation certificate list ``` Upon successful listing, all the certificate files in the trust store are printed out with information of store type, store name and certificate file name. If the listing fails, an error message is printed out with specific reasons. Nothing is printed out if the trust store is empty. An example of the output: ``` STORE TYPE STORE NAME CERTIFICATE ca myStore1 cert1.pem ca myStore2 cert2.crt signingAuthority myStore1 cert3.crt signingAuthority myStore2 cert4.pem ``` ### List all certificate files of a certain named store ```bash notation cert list --store ``` Upon successful listing, all the certificate files in the trust store named `` are printed out with information of store type, store name and certificate file name. If the listing fails, an error message is printed out with specific reasons. Nothing is printed out if the trust store is empty. ### List all certificate files of a certain type of store ```bash notation cert list --type ``` Upon successful listing, all the certificate files in the trust store of type `` are printed out with information of store type, store name and certificate file name. If the listing fails, an error message is printed out with specific reasons. Nothing is printed out if the trust store is empty. ### List all certificate files of a certain named store of a certain type ```bash notation cert list --type --store ``` Upon successful listing, all the certificate files in the trust store named `` of type `` are printed out with information of store type, store name and certificate file name. If the listing fails, an error message is printed out with specific reasons. Nothing is printed out if the trust store is empty. ### Show details of a certain certificate file ```bash notation certificate show --type --store ``` Upon successful show, the certificate details are printed out starting from leaf certificate if it's a certificate chain. Here is a list of certificate properties: * Issuer * Subject * Valid from * Valid to * IsCA * Thumbprints If the showing fails, an error message is printed out with specific reasons. ### Delete all certificates of a certain named store of a certain type ```bash notation certificate delete --type --store --all ``` A prompt is showed asking user to confirm the deletion. Upon successful deletion, all certificates in trust store named `` of type `` are deleted. If deletion fails, a list of successful deleted certificate files is printed out as well as a list of deletion-failure certificates with specific reasons. ### Delete a specific certificate of a certain named store of a certain type ```bash notation certificate delete --type --store ``` A prompt is displayed, asking the user to confirm the deletion. Upon successful deletion, the specific certificate is deleted from the trust store named `` of type ``. The output message is printed out as following: ```text Successfully deleted from the trust store. ``` If users execute the deletion without specifying required flags using `notation cert delete `, the deletion fails and the error output message is printed out as follows: ```text Error: required flag(s) "store", "type" not set ``` ### Generate a local RSA key and a corresponding self-generated certificate for testing purpose and add the certificate into trust store ```bash notation certificate generate-test "wabbit-networks.io" ``` Upon successful execution, a local key file and certificate file named `wabbit-networks.io` are generated and stored in `$XDG_CONFIG_HOME/notation/localkeys/`. `wabbit-networks.io` is also used as certificate subject.CommonName. notation-1.2.0/specs/commandline/inspect.md000066400000000000000000000440761466375446100207720ustar00rootroot00000000000000# notation inspect ## Description Use `notation inspect` command to inspect all the signatures associated with artifacts stored in OCI compliant registries in a human readable format. Upon successful execution, both the digest of the signed artifact and the digests of signatures manifest along with their properties associated with the signed artifact are printed in the following format: ```shell /@ └── application/vnd.cncf.notary.signature ├── │ ├── │ ├── │ ├── │ ├── │ ├── │ └── └── ├── ├── ├── ├── └── ``` ## Outline ```text Inspect all signatures associated with the signed artifact. Usage: notation inspect [flags] Flags: -d, --debug debug mode -h, --help help for inspect --insecure-registry use HTTP protocol while connecting to registries. Should be used only for testing --max-signatures int maximum number of signatures to evaluate or examine (default 100) -o, --output string output format, options: 'json', 'text' (default "text") -p, --password string password for registry operations (default to $NOTATION_PASSWORD if not specified) -u, --username string username for registry operations (default to $NOTATION_USERNAME if not specified) -v, --verbose verbose mode ``` ## Usage ### Display the details of all the listed signatures and its associated certificate properties of the signed container image ```text notation inspect [flags] /@ ``` ## Inspect signatures on the supplied OCI artifact identified by the digest ```shell # Prerequisites: Signatures are stored in a registry referencing the signed OCI artifact notation inspect localhost:5000/net-monitor@sha256:b94d27b9934d3e08a52e52d7da1ac484efe37a5380ee9088f7ace2efcde9 ``` An example output: ```shell Inspecting all signatures for signed artifact localhost:5000/net-monitor@sha256:b94d27b9934d3e08a52e52d7da7dabfac4efe37a5380ee9088f7ace2efcde9 └── application/vnd.cncf.notary.signature ├── sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa │ ├── signature algorithm: RSASSA-PSS-SHA-256 │ ├── signed attributes │ │ ├── content type: application/vnd.cncf.notary.payload.v1+json │ │ ├── signing scheme: notary.default.x509 │ │ ├── signing time: Fri Jun 23 22:04:01 2023 │ │ ├── expiry: Sat Jun 29 22:04:01 2024 │ │ └── io.cncf.notary.verificationPlugin: com.example.nv2plugin //extended attributes │ ├── user defined attributes │ │ └── io.wabbit-networks.buildId: 123 //user defined metadata │ ├── unsigned attributes │ │ ├── timestamp signature //TSA response | │ │ ├── timestamp: [Fri Jun 23 22:04:31 2023, Fri Jun 23 22:04:31 2023] | │ │ └── certificates | │ │ └── SHA256 fingerprint: d2f6e46ded7422ccd1d440576841366f828ada559aae3316af4d1a9ad40c7828 | │ │ ├── issued to: wabbit-com Software Timestamp | │ │ ├── issued by: wabbit-com Software Trusted Timestamping | │ │ └── expiry: Fri Oct 13 23:59:59 2034 │ │ └── io.cncf.notary.signingAgent: notation/1.0.0 //client version │ ├── certificates │ │ ├── SHA256 fingerprint: E8C15B4C98AD91E051EE5AF5F524A8729050B2A │ │ │ ├── issued to: wabbit-com Software │ │ │ ├── issued by: wabbit-com Software Root Certificate Authority │ │ │ └── expiry: Sun Jul 06 20:50:17 2025 │ │ ├── SHA256 fingerprint: 4b9fa61d5aed0fabbc7cb8fe2efd049da57957ed44f2b98f7863ce18effd3b89 │ │ │ ├── issued to: wabbit-com Software Code Signing PCA │ │ │ ├── issued by: wabbit-com Software Root Certificate Authority │ │ │ └── expiry: Sun Jul 06 20:50:17 2025 │ │ └── SHA256 fingerprint: ea3939548ad0c0a86f164ab8b97858854238c797f30bddeba6cb28688f3f6536 │ │ ├── issued to: wabbit-com Software Root Certificate Authority │ │ ├── issued by: wabbit-com Software Root Certificate Authority │ │ └── expiry: Sat Jun 23 22:04:01 2035 │ └── signed artifact //descriptor of signed artifact │ ├── media type: application/vnd.oci.image.manifest.v1+json │ ├── digest: sha256:b94d27b9934d3e08a52e52d7da7dabfac48437a5380ee9088f7ace2efcde9 │ └── size: 16724 └── sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb ├── signature algorithm: RSASSA-PSS-SHA-256 ├── signed attributes │ ├── content type: application/vnd.cncf.notary.payload.v1+json │ ├── signing scheme: notary.signingAuthority.x509 │ ├── signing time: Fri Jun 23 22:04:01 2023 │ ├── expiry: Sat Jun 29 22:04:01 2024 │ └── io.cncf.notary.verificationPlugin: com.example.nv2plugin ├── unsigned attributes │ ├── timestamp signature │ │ ├── timestamp: [Fri Jun 23 22:04:31 2023, Fri Jun 23 22:04:31 2023] │ │ └── certificates │ │ └── SHA256 fingerprint: d2f6e46ded7422ccd1d440576841366f828ada559aae3316af4d1a9ad40c7828 │ │ ├── issued to: wabbit-com Software Timestamp │ │ ├── issued by: wabbit-com Software Trusted Timestamping │ │ └── expiry: Fri Oct 13 23:59:59 2034 │ └── io.cncf.notary.signingAgent: notation/1.0.0 ├── certificates │ ├── SHA256 fingerprint: b13a843be16b1f461f08d61c14f3eab7d87c073570da077217541a7eb31c084d │ │ ├── issued to: wabbit-com Software │ │ ├── issued by: wabbit-com Software Root Certificate Authority │ │ └── expiry: Sun Jul 06 20:50:17 2025 │ ├── SHA256 fingerprint: 4b9fa61d5aed0fabbc7cb8fe2efd049da57957ed44f2b98f7863ce18effd3b89 │ │ ├── issued to: wabbit-com Software Code Signing PCA 2010 │ │ ├── issued by: wabbit-com Software Root Certificate Authority │ │ └── expiry: Sun Jul 06 20:50:17 2025 │ └── SHA256 fingerprint: ea3939548ad0c0a86f164ab8b97858854238c797f30bddeba6cb28688f3f6536 │ ├── issued to: wabbit-com Software Root Certificate Authority │ ├── issued by: wabbit-com Software Root Certificate Authority │ └── expiry: Sat Jun 23 22:04:01 2035 └── signed artifact ├── media type: application/vnd.oci.image.manifest.v1+json ├── digest: sha256:b94d27b9934d3e08a52e52d7da7fac484efe37a5380ee9088f7ace2efcde9 └── size: 16724 ``` ## Inspect signatures on an OCI artifact identified by a tag `Tags` are mutable, but `Digests` uniquely and immutably identify an artifact. If a tag is used to identify a signed artifact, notation resolves the tag to the `digest` first. ```shell # Prerequisites: Signatures are stored in a registry referencing the signed OCI artifact notation inspect localhost:5000/net-monitor:v1 ``` An example output: ```text Resolved artifact tag `v1` to digest `sha256:b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9` before inspect. Warning: The resolved digest may not point to the same signed artifact, since tags are mutable. ``` ```shell Inspecting all signatures for signed artifact localhost:5000/net-monitor@sha256:ca5427b5567d3e06a72e52d7da7dabfac484efe37a5380ee9088f7ace2eaab9 └── application/vnd.cncf.notary.signature ├── sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa │ ├── signature algorithm: RSASSA-PSS-SHA-256 │ ├── signed attributes │ │ ├── content type: application/vnd.cncf.notary.payload.v1+json │ │ ├── signing scheme: notary.default.x509 │ │ ├── signing time: Fri Jun 23 22:04:01 2023 │ │ ├── expiry: Sat Jun 29 22:04:01 2024 │ │ └── io.cncf.notary.verificationPlugin: com.example.nv2plugin │ ├── user defined attributes │ │ └── io.wabbit-networks.buildId: 123 │ ├── unsigned attributes │ │ ├── timestamp signature | │ │ ├── timestamp: [Fri Jun 23 22:04:31 2023, Fri Jun 23 22:04:31 2023] | │ │ └── certificates | │ │ └── SHA256 fingerprint: d2f6e46ded7422ccd1d440576841366f828ada559aae3316af4d1a9ad40c7828 | │ │ ├── issued to: wabbit-com Software Timestamp | │ │ ├── issued by: wabbit-com Software Trusted Timestamping | │ │ └── expiry: Fri Oct 13 23:59:59 2034 │ │ └── io.cncf.notary.signingAgent: notation/1.0.0 │ ├── certificates │ │ ├── SHA256 fingerprint: b13a843be16b1f461f08d61c14f3eab7d87c073570da077217541a7eb31c084d │ │ │ ├── issued to: wabbit-com Software │ │ │ ├── issued by: wabbit-com Software Root Certificate Authority │ │ │ └── expiry: Sun Jul 06 20:50:17 2025 │ │ ├── SHA256 fingerprint: 4b9fa61d5aed0fabbc7cb8fe2efd049da57957ed44f2b98f7863ce18effd3b89 │ │ │ ├── issued to: wabbit-com Software Code Signing PCA │ │ │ ├── issued by: wabbit-com Software Root Certificate Authority │ │ │ └── expiry: Sun Jul 06 20:50:17 2025 │ │ └── SHA256 fingerprint: ea3939548ad0c0a86f164ab8b97858854238c797f30bddeba6cb28688f3f6536 │ │ ├── issued to: wabbit-com Software Root Certificate Authority │ │ ├── issued by: wabbit-com Software Root Certificate Authority │ │ └── expiry: Sat Jun 23 22:04:01 2035 │ └── signed artifact │ ├── media type: application/vnd.oci.image.manifest.v1+json │ ├── digest: sha256:ca5427b5567d3e06a72e52d7da7dabfac484efe37a5380ee9088f7ace2eaab9 │ └── size: 16724 └── sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb ├── signature algorithm: RSASSA-PSS-SHA-256 ├── signed attributes │ ├── content type: application/vnd.cncf.notary.payload.v1+json │ ├── signing scheme: notary.signingAuthority.x509 │ ├── signing time: Fri Jun 23 22:04:01 2023 │ ├── expiry: Sat Jun 29 22:04:01 2024 │ └── io.cncf.notary.verificationPlugin: com.example.nv2plugin ├── unsigned attributes │ ├── timestamp signature │ │ ├── timestamp: [Fri Jun 23 22:04:31 2023, Fri Jun 23 22:04:31 2023] │ │ └── certificates │ │ └── SHA256 fingerprint: d2f6e46ded7422ccd1d440576841366f828ada559aae3316af4d1a9ad40c7828 │ │ ├── issued to: wabbit-com Software Timestamp │ │ ├── issued by: wabbit-com Software Trusted Timestamping │ │ └── expiry: Fri Oct 13 23:59:59 2034 │ └── io.cncf.notary.signingAgent: notation/1.0.0 ├── certificates │ ├── SHA256 fingerprint: b13a843be16b1f461f08d61c14f3eab7d87c073570da077217541a7eb31c084d │ │ ├── issued to: wabbit-com Software │ │ ├── issued by: wabbit-com Software Root Certificate Authority │ │ └── expiry: Sun Jul 06 20:50:17 2025 │ ├── SHA256 fingerprint: 4b9fa61d5aed0fabbc7cb8fe2efd049da57957ed44f2b98f7863ce18effd3b89 │ │ ├── issued to: wabbit-com Software Code Signing PCA │ │ ├── issued by: wabbit-com Software Root Certificate Authority │ │ └── expiry: Sun Jul 06 20:50:17 2025 │ └── SHA256 fingerprint: ea3939548ad0c0a86f164ab8b97858854238c797f30bddeba6cb28688f3f6536 │ ├── issued to: wabbit-com Software Root Certificate Authority │ ├── issued by: wabbit-com Software Root Certificate Authority │ └── expiry: Sat Jun 23 22:04:01 2035 └── signed artifact ├── media type: application/vnd.oci.image.manifest.v1+json ├── digest: sha256:ca5427b5567d3e06a72e52d7da7dabfac484efe37a5380ee9088f7ace2eaab9 └── size: 16724 ``` ## Inspect signatures on the supplied OCI artifact with an example of JSON Output ```shell notation inspect localhost:5000/net-monitor@sha256:b94d27b9934d3e08a52e52da7dabfac484efe37a5380ee9088f7ace2efcde9 -o json ``` An example output: ```jsonc { "mediaType": "application/vnd.oci.image.manifest.v1+json", "signatures": [ { "digest": "sha256:73c803930ea3ba1e54bc25c2bdc53edd0284c62ed651fe7b00369da519a33", "signatureAlgorithm": "RSASSA-PSS-SHA-256", "signedAttributes": { "contentType": "application/vnd.cncf.notary.payload.v1+json", "signingScheme": "notary.default.x509", "signingTime": "2022-02-06T20:50:17Z", "expiry": "2023-02-06T20:50:17Z", "io.cncf.notary.verificationPlugin": "com.example.nv2plugin" }, "userDefinedAttributes": { "io.wabbit-networks.buildId": "123" }, "unsignedAttributes": { "timestampSignature": { "timestamp": "[2022-02-06T20:50:37Z, 2022-02-06T20:50:37Z]", "certificates": [ { "SHA256Fingerprint": "d2f6e46ded7422ccd1d440576841366f828ada559aae3316af4d1a9ad40c7828", "issuedTo": "wabbit-com Software Timestamp", "issuedBy": "wabbit-com Software Trusted Timestamping", "expiry": "2034-10-13T23:59:59Z" } ] }, "signingAgent": "notation/1.0.0" }, "certificates": [ { "SHA256Fingerprint": "b13a843be16b1f461f08d61c14f3eab7d87c073570da077217541a7eb31c084d", "issuedTo": "wabbit-com Software", "issuedBy": "wabbit-com Software Root Certificate Authority", "expiry": "2025-07-06T20:50:17Z" }, { "SHA256Fingerprint": "4b9fa61d5aed0fabbc7cb8fe2efd049da57957ed44f2b98f7863ce18effd3b89", "issuedTo": "wabbit-com Software Code Signing PCA", "issuedBy": "wabbit-com Software Root Certificate Authority", "expiry": "2025-07-06T20:50:17Z" }, { "SHA256Fingerprint": "ea3939548ad0c0a86f164ab8b97858854238c797f30bddeba6cb28688f3f6536", "issuedTo": "wabbit-com Software Root Certificate Authority", "issuedBy": "wabbit-com Software Root Certificate Authority", "expiry": "2035-07-06T20:50:17Z" } ], "signedArtifact": { "mediaType": "application/vnd.oci.image.manifest.v1+json", "digest": "sha256:73c803930ea3ba1e54bc25c2bdc53edd0284c62ed651fe7b00369519a3c333", "size": 16724 } }, { "digest": "sha256:73c803930ea3ba1e54bc25c2bdc53edd0284c62ed651fe7b00369da519a3c333", "signatureAlgorithm": "RSASSA-PSS-SHA-256", "signedAttributes": { "contentType": "application/vnd.cncf.notary.payload.v1+json", "signingScheme": "notary.signingAuthority.x509", "signingTime": "2022-02-06T20:50:17Z", "expiry": "2023-02-06T20:50:17Z", "io.cncf.notary.verificationPlugin": "com.example.nv2plugin" }, "unsignedAttributes": { "timestampSignature": { "timestamp": "[2022-02-06T20:50:37Z, 2022-02-06T20:50:37Z]", "certificates": [ { "SHA256Fingerprint": "d2f6e46ded7422ccd1d440576841366f828ada559aae3316af4d1a9ad40c7828", "issuedTo": "wabbit-com Software Timestamp", "issuedBy": "wabbit-com Software Trusted Timestamping", "expiry": "2034-10-13T23:59:59Z" } ] }, "signingAgent": "notation/1.0.0" }, "certificates": [ { "SHA256Fingerprint": "b13a843be16b1f461f08d61c14f3eab7d87c073570da077217541a7eb31c084d", "issuedTo": "wabbit-com Software", "issuedBy": "wabbit-com Software Root Certificate Authority", "expiry": "2025-07-06T20:50:17Z" }, { "SHA256Fingerprint": "4b9fa61d5aed0fabbc7cb8fe2efd049da57957ed44f2b98f7863ce18effd3b89", "issuedTo": "wabbit-com Software Code Signing PCA", "issuedBy": "wabbit-com Software Root Certificate Authority", "expiry": "2025-07-06T20:50:17Z" }, { "SHA256Fingerprint": "ea3939548ad0c0a86f164ab8b97858854238c797f30bddeba6cb28688f3f6536", "issuedTo": "wabbit-com Software Root Certificate Authority", "issuedBy": "wabbit-com Software Root Certificate Authority", "expiry": "2035-07-06T20:50:17Z" } ], "signedArtifact": { "mediaType": "application/vnd.oci.image.manifest.v1+json", "digest": "sha256:73c803930ea3ba1e54bc25c2bdc53edd0284c62ed651fe7b069da519a3c333", "size": 16724 } } ] } ``` notation-1.2.0/specs/commandline/key.md000066400000000000000000000100041466375446100200750ustar00rootroot00000000000000# notation key ## Description Use ```notation key``` command to manage keys used for signing. User can add/update/list/remove key to/from Notation signing key list. Please be noted this command doesn't manage the lifecycle of signing key itself, it manages the Notation signing key list only. ## Outline ### notation key command ```text Manage keys used for signing Usage: notation key [command] Available Commands: add Add key to Notation signing key list delete Remove key from Notation signing key list list List keys used for signing update Update key in Notation signing key list Flags: -h, --help help for key ``` ### notation key add ```text Add key to Notation signing key list Usage: notation key add --plugin [flags] Flags: -d, --debug debug mode --default mark as default -h, --help help for add --id string key id (required if --plugin is set) --plugin string signing plugin name --plugin-config stringArray {key}={value} pairs that are passed as it is to a plugin, refer plugin's documentation to set appropriate values -v, --verbose verbose mode ``` ### notation key delete ```text Remove key from Notation signing key list Usage: notation key delete [flags] ... Flags: -d, --debug debug mode -h, --help help for delete -v, --verbose verbose mode ``` ### notation key list ```text List keys used for signing Usage: notation key list [flags] Aliases: list, ls Flags: -h, --help help for list ``` ### notation key update ```text Update key in Notation signing key list Usage: notation key update [flags] Aliases: update, set Flags: -d, --debug debug mode --default mark as default -h, --help help for update -v, --verbose verbose mode ``` ## Usage ### Add a default signing key referencing the key identifier for the remote key, and the plugin associated with it ```shell notation key add --default --plugin --id ``` Upon successful adding, a key name is printed out for added signing key with additional info "marked as default". ### Update the default signing key ```shell notation key update --default ``` Upon successful update, the supplied key name is printed out with additional info "marked as default". ### List signing keys ```text notation key list ``` Upon successful execution, a list of keys is printed out with information of name, key path, certificate path, key id and plugin name. The default signing key name is preceded by an asterisk. The key id and plugin name are used together to provide the information of the key identifier for the remote key and the plugin associated with it. ### Remove a specified key from Notation signing key list ```shell notation key delete ``` - Upon successful removal of a local testing key created by notation, the output message is printed out as follows: ```text Removed from Notation signing key list. The source key still exists. ``` - Upon successful removal of a key associated with a KMS, the output message is printed out as follows: ```text Removed from Notation signing key list. The source key still exists. ``` - Upon successful removal of the default key, the output message is printed out as follows: ```text Removed default key from Notation signing key list. The source key still exists. ``` ### Remove two keys from Notation signing key list ```shell notation key delete ``` Upon successful execution, the output message is printed out as follows. Please be noted if default signing key is removed, Notation will not automatically assign a new default signing key. User needs to update the default signing key explicitly. ```text Removed the following keys from Notation signing key list. The source keys still exist. (default) ```notation-1.2.0/specs/commandline/list.md000066400000000000000000000056751466375446100203020ustar00rootroot00000000000000# notation list ## Description Use `notation list` to list all the signatures associated with a signed OCI artifact. `Tags` are mutable, but `Digests` uniquely and immutably identify an artifact. If a tag is used to identify a signed artifact, notation resolves the tag to the `digest` first. Upon successful execution, both the digest of the signed artifact and the digests of signatures manifest associated with signed artifact are printed out as following: ```shell /@ └── application/vnd.cncf.notary.signature ├── └── ``` ## Outline ```text List all the signatures associated with a signed OCI artifact Usage: notation list [flags] Aliases: list, ls Flags: -d, --debug debug mode -h, --help help for list --insecure-registry use HTTP protocol while connecting to registries. Should be used only for testing --max-signatures int maximum number of signatures to evaluate or examine (default 100) --oci-layout [Experimental] list signatures stored in OCI image layout -p, --password string password for registry operations (default to $NOTATION_PASSWORD if not specified) -u, --username string username for registry operations (default to $NOTATION_USERNAME if not specified) -v, --verbose verbose mode ``` ## Usage ### List all the signatures of the signed container image ```shell notation list /: ``` An example output: ```shell localhost:5000/net-monitor@sha256:8456f085dd609fd12cdebc5f80b6f33f25f670a7a9a03c8fa750b8aee0c4d657 └── application/vnd.cncf.notary.signature ├── sha256:647039638efb22a021f59675c9449dd09956c981a44b82c1ff074513c2c9f273 └── sha256:6bfb3c4fd485d6810f9656ddd4fb603f0c414c5f0b175ef90eeb4090ebd9bfa1 ``` ### [Experimental] List all the signatures associated with the image in OCI layout directory The following example lists the signatures associated with the image in OCI layout directory named `hello-world`. To access this flag `--oci-layout` , set the environment variable `NOTATION_EXPERIMENTAL=1`. Reference an image in OCI layout directory using tags: ```shell export NOTATION_EXPERIMENTAL=1 # Assume OCI layout directory hello-world is under current path notation list --oci-layout hello-world:v1 ``` Reference an image in OCI layout directory using exact digest: ```shell export NOTATION_EXPERIMENTAL=1 # Assume OCI layout directory hello-world is under current path notation list --oci-layout hello-world@sha256:xxx ``` An example output: ```shell hello-world@sha256:a08753c0c7bcdaaf5c2fdb375f68e860c34bffb146368982c201d41769e1763c └── application/vnd.cncf.notary.signature ├── sha256:647039638efb22a021f59675c9449dd09956c981a44b82c1ff074513c2c9f273 └── sha256:6bfb3c4fd485d6810f9656ddd4fb603f0c414c5f0b175ef90eeb4090ebd9bfa1 ``` notation-1.2.0/specs/commandline/login.md000066400000000000000000000021311466375446100204170ustar00rootroot00000000000000# notation login ## Description Use `notation login` to log in to an OCI registry. Users can execute `notation login` multiple times to log in multiple registries. ## Outline ```text Log in to an OCI registry Usage: notation login [flags] Flags: -d, --debug debug mode -h, --help help for login --insecure-registry use HTTP protocol while connecting to registries. Should be used only for testing -p, --password string password for registry operations (default to $NOTATION_PASSWORD if not specified) --password-stdin take the password from stdin -u, --username string username for registry operations (default to $NOTATION_USERNAME if not specified) -v, --verbose verbose mode ``` ## Usage ### Log in with provided username and password ```shell notation login -u -p registry.example.com ``` ### Log in using $NOTATION_USERNAME $NOTATION_PASSWORD variables ```shell # Prerequisites: # set environment variable NOTATION_USERNAME and NOTATION_PASSWORD notation login registry.example.com ``` notation-1.2.0/specs/commandline/logout.md000066400000000000000000000006111466375446100206210ustar00rootroot00000000000000# notation logout ## Description Use `notation logout` to log out from an OCI registry. ## Outline ```text Log out from the logged in registries Usage: notation logout [flags] Flags: -d, --debug debug mode -h, --help help for logout -v, --verbose verbose mode ``` ## Usage ### Log out from an OCI registry ```shell notation logout registry.example.com ``` notation-1.2.0/specs/commandline/plugin.md000066400000000000000000000133371466375446100206170ustar00rootroot00000000000000# notation plugin ## Description Use `notation plugin` to manage plugins. See notation [plugin documentation](https://github.com/notaryproject/notaryproject/blob/v1.1.0-rc.1/specs/plugin-extensibility.md) for more details. The `notation plugin` command by itself performs no action. In order to manage notation plugins, one of the subcommands must be used. ## Outline ### notation plugin ```text Manage plugins Usage: notation plugin [command] Available Commands: install Install a plugin list List installed plugins uninstall Uninstall a plugin Flags: -h, --help help for plugin ``` ### notation plugin list ```text List installed plugins Usage: notation plugin list [flags] Flags: -h, --help help for list Aliases: list, ls ``` ### notation plugin install ```text Install a plugin Usage: notation plugin install [flags] <--file|--url> Flags: -d, --debug debug mode --file install plugin from a file on file system --force force the installation of the plugin -h, --help help for install --sha256sum string must match SHA256 of the plugin source, required when "--url" flag is set --url install plugin from an HTTPS URL. The plugin download timeout is 10m0s -v, --verbose verbose mode Aliases: install, add ``` ### notation plugin uninstall ```text Uninstall a plugin Usage: notation plugin uninstall [flags] Flags: -d, --debug debug mode -h, --help help for remove -v, --verbose verbose mode -y, --yes do not prompt for confirmation Aliases: uninstall, remove, rm ``` ## Usage ## Install a plugin ### Install a plugin from file system Install a Notation plugin from the host file system. `.zip`, `.tar.gz`, and `single plugin executable file` formats are supported. In this scenario, SHA-256 checksum validation is optional. ```shell $ notation plugin install --file ``` Upon successful execution, the plugin is copied to Notation's plugin directory. If the plugin directory does not exist, it will be created. The name and version of the installed plugin are displayed as follows. ```console Successfully installed plugin , version ``` If the entered plugin checksum digest doesn't match the published checksum, Notation will return an error message and will not start installation. ```console Error: plugin installation failed: plugin sha256sum does not match user input. Expecting ``` If the plugin version is higher than the existing plugin, Notation will start installation and overwrite the existing plugin. ```console Successfully updated plugin from version to ``` If the plugin version is equal to the existing plugin, Notation will not start installation and return the following message. Users can use a flag `--force` to skip plugin version check and force the installation. ```console Error: plugin installation failed: plugin with version already exists. ``` If the plugin version is lower than the existing plugin, Notation will return an error message and will not start installation. Users can use a flag `--force` to skip plugin version check and force the installation. ```console Error: plugin installation failed: failed to install plugin . The installing plugin version is lower than the existing plugin version . It is not recommended to install an older version. To force the installation, use the "--force" option. ``` ### Install a plugin from URL Install a Notation plugin from a URL. Notation only supports HTTPS URL, which means that the URL must start with "https://". The URL MUST point to a resource in `.zip`, `.tar.gz`, or `single plugin executable file` format. In this scenario, the SHA-256 checksum of the resource MUST be provided. ```shell $ notation plugin install --sha256sum --url ``` ### Uninstall a plugin ```shell notation plugin uninstall ``` Upon successful execution, the plugin is uninstalled from the plugin directory. ```shell Are you sure you want to uninstall plugin ""? [y/n] y Successfully uninstalled plugin ``` Uninstall the plugin without prompt for confirmation. ```shell notation plugin uninstall --yes ``` If the plugin is not found, an error is returned showing the syntax for the plugin list command to show the installed plugins. ```shell Error: unable to find plugin . To view a list of installed plugins, use `notation plugin list` ``` ### List installed plugins ```shell notation plugin list ``` Upon successful execution, a list of plugins are printed out with information of name, description, version, capabilities and errors. The capabilities show what the plugin is capable of, for example, the plugin can generate signatures or verify signatures. The errors column indicates whether the plugin was installed properly or not. `` of Error indicates that the plugin installed successfully. An example of output from `notation plugin list`: ```text NAME DESCRIPTION VERSION CAPABILITIES ERROR azure-kv Sign artifacts with keys in Azure Key Vault v1.0.0 Signature generation com.amazonaws.signer.notation.plugin AWS Signer plugin for Notation 1.0.290 Signature envelope generation, Trusted Identity validation, Certificate chain revocation check ``` notation-1.2.0/specs/commandline/policy.md000066400000000000000000000135531466375446100206200ustar00rootroot00000000000000# notation policy ## Description As part of signature verification workflow of signed OCI artifacts, users need to configure trust policy configuration file to specify trusted identities that signed the artifacts, the level of signature verification to use and other settings. For more details, see [trust policy specification and examples](https://github.com/notaryproject/specifications/blob/v1.1.0-rc.1/specs/trust-store-trust-policy.md#trust-policy). The `notation policy` command provides a user-friendly way to manage trust policies for signed OCI images. It allows users to show trust policy configuration, import/export a trust policy configuration file from/to a JSON file. To get started, user can refer to the following trust policy configuration sample `trustpolicy.json` that is applicable for verifying signed OCI artifacts using `notation verify` command. In this sample, there are four policies configured for different requirements: - The Policy named "wabbit-networks-images" is for verifying OCI artifacts signed by Wabbit Networks and stored in two repositories `registry.acme-rockets.io/software/net-monitor` and `registry.acme-rockets.io/software/net-logger`. - Policy named "unsigned-image" is for skipping the verification on unsigned OCI artifacts stored in repository `registry.acme-rockets.io/software/unsigned/net-utils`. - Policy "allow-expired-images" is for logging instead of failing expired OCI artifacts stored in repository `registry.acme-rockets.io/software/legacy/metrics`. - Policy "global-policy-for-all-other-images" is for verifying any other OCI artifacts that signed by the ACME Rockets. ```jsonc { "version": "1.0", "trustPolicies": [ { "name": "wabbit-networks-images", "registryScopes": [ "registry.acme-rockets.io/software/net-monitor", "registry.acme-rockets.io/software/net-logger" ], "signatureVerification": { "level": "strict" }, "trustStores": [ "ca:wabbit-networks", ], "trustedIdentities": [ "x509.subject: C=US, ST=WA, L=Seattle, O=wabbit-networks.io, OU=Security Tools" ] }, { "name": "unsigned-image", "registryScopes": [ "registry.acme-rockets.io/software/unsigned/net-utils" ], "signatureVerification": { "level" : "skip" } }, { "name": "allow-expired-images", "registryScopes": [ "registry.acme-rockets.io/software/legacy/metrics" ], "signatureVerification": { "level" : "strict", "override" : { "expiry" : "log" } }, "trustStores": ["ca:acme-rockets"], "trustedIdentities": ["*"] }, { "name": "global-policy-for-all-other-images", "registryScopes": [ "*" ], "signatureVerification": { "level": "strict" }, "trustStores": [ "ca:acme-rockets" ], "trustedIdentities": [ "x509.subject: C=US, ST=WA, L=Seattle, O=acme-rockets.io, CN=SecureBuilder" ] } ] } ``` ## Outline ### notation policy command ```text Manage trust policy configuration for signature verification. Usage: notation policy [command] Available Commands: import import trust policy configuration from a JSON file show show trust policy configuration Flags: -h, --help help for policy ``` ### notation policy import ```text Import trust policy configuration from a JSON file Usage: notation policy import [flags] Flags: --force override the existing trust policy configuration, never prompt -h, --help help for import ``` ### notation policy show ```text Show trust policy configuration Usage: notation policy show [flags] Flags: -h, --help help for show ``` ## Usage ### Import trust policy configuration from a JSON file An example of import trust policy configuration from a JSON file: ```shell notation policy import ./my_policy.json ``` The trust policy configuration in the JSON file should be validated according to [trust policy properties](https://github.com/notaryproject/specifications/blob/v1.1.0-rc.1/specs/trust-store-trust-policy.md#trust-policy-properties). A successful message should be printed out if trust policy configuration are imported successfully. Error logs including the reason should be printed out if the importing fails. If there is an existing trust policy configuration, prompt for users to confirm whether discarding existing configuration or not. Users can use `--force` flag to discard existing trust policy configuration without prompt. ### Show trust policies Use the following command to show trust policy configuration: ```shell notation policy show ``` Upon successful execution, the trust policy configuration is printed out to standard output. If trust policy is not configured or is malformed, users should receive an error message via standard error output, and a tip to import trust policy configuration from a JSON file. ### Export trust policy configuration into a JSON file Users can redirect the output of command `notation policy show` to a JSON file. ```shell notation policy show > ./trustpolicy.json ``` ### Update trust policy configuration The steps to update trust policy configuration: 1. Export trust policy configuration into a JSON file. ```shell notation policy show > ./trustpolicy.json ``` 2. Edit the exported JSON file "trustpolicy.json", update trust policy configuration and save the file. 3. Import trust policy configuration from the file. ```shell notation policy import ./trustpolicy.json ``` notation-1.2.0/specs/commandline/sign.md000066400000000000000000000226761466375446100202670ustar00rootroot00000000000000# notation sign ## Description Use `notation sign` to sign artifacts stored in OCI compliant registries. Signs an OCI artifact stored in the registry. Always sign artifact using digest(`@sha256:...`) rather than a tag(`:v1`) because tags are mutable and a tag reference can point to a different artifact than the one signed. If a tag is used, notation resolves the tag to the `digest` before signing. Upon successful signing, the generated signature is pushed to the registry and associated with the signed OCI artifact. The output message is printed out as following: ```text Successfully signed /@ ``` If a `tag` is used to identify the OCI artifact, the output message is as following: ```test Warning: Always sign the artifact using digest(`@sha256:...`) rather than a tag(`:`) because tags are mutable and a tag reference can point to a different artifact than the one signed. Successfully signed /@ ``` ## Outline ```text Sign artifacts Usage: notation sign [flags] Flags: --force-referrers-tag force to store signatures using the referrers tag schema (default true) -d, --debug debug mode -e, --expiry duration optional expiry that provides a "best by use" time for the artifact. The duration is specified in minutes(m) and/or hours(h). For example: 12h, 30m, 3h20m -h, --help help for sign --id string key id (required if --plugin is set). This is mutually exclusive with the --key flag --insecure-registry use HTTP protocol while connecting to registries. Should be used only for testing -k, --key string signing key name, for a key previously added to notation's key list. This is mutually exclusive with the --id and --plugin flags --oci-layout [Experimental] sign the artifact stored as OCI image layout -p, --password string password for registry operations (default to $NOTATION_PASSWORD if not specified) --plugin string signing plugin name. This is mutually exclusive with the --key flag --plugin-config stringArray {key}={value} pairs that are passed as it is to a plugin, refer plugin's documentation to set appropriate values. --signature-format string signature envelope format, options: "jws", "cose" (default "jws") --timestamp-root-cert string filepath of timestamp authority root certificate --timestamp-url string RFC 3161 Timestamping Authority (TSA) server URL -u, --username string username for registry operations (default to $NOTATION_USERNAME if not specified) -m, --user-metadata stringArray {key}={value} pairs that are added to the signature payload -v, --verbose verbose mode ``` ### Set config property for OCI image manifest Notation uses [OCI image manifest][oci-image-spec] to store signatures in registries. The empty JSON object `{}` is used as the default configuration content, and thus the `config` property is fixed, as following: ```json "config": { "mediaType": "application/vnd.cncf.notary.signature", "size": 2, "digest": "sha256:44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a" } ``` ## Usage ### Sign an OCI artifact by adding new key ```shell # Prerequisites: # - A signing plugin is installed. See plugin documentation (https://github.com/notaryproject/notaryproject/blob/v1.1.0-rc.1/specs/plugin-extensibility.md) for more details. # - Configure the signing plugin as instructed by plugin vendor. # Add a default signing key referencing the remote key identifier, and the plugin associated with it. notation key add --default --name --plugin --id # sign an artifact stored in a registry notation sign /@ ``` An example for a successful signing: ```console $ notation sign localhost:5000/net-monitor@sha256:b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9 Successfully signed localhost:5000/net-monitor@sha256:b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9 ``` ### Sign an OCI artifact with on-demand remote key ```shell notation sign --plugin --id /@ ``` ### Sign an OCI artifact using COSE signature format ```shell # Prerequisites: # A default signing key is configured using CLI "notation key" # Use option "--signature-format" to set the signature format to COSE. notation sign --signature-format cose /@ ``` ### Sign an OCI artifact stored in a registry using the default signing key ```shell # Prerequisites: # A default signing key is configured using CLI "notation key" # Use a digest that uniquely and immutably identifies an OCI artifact. notation sign /@ ``` ### Sign an OCI Artifact with user metadata ```shell # Prerequisites: # A default signing key is configured using CLI "notation key" # sign an artifact stored in a registry and add user-metadata io.wabbit-networks.buildId=123 to the payload notation sign --user-metadata io.wabbit-networks.buildId=123 /@ # sign an artifact stored in a registry and add user-metadata io.wabbit-networks.buildId=123 and io.wabbit-networks.buildTime=1672944615 to the payload notation sign --user-metadata io.wabbit-networks.buildId=123 --user-metadata io.wabbit-networks.buildTime=1672944615 /@ ``` ### Sign an OCI artifact stored in a registry and specify the signature expiry duration, for example 24 hours ```shell notation sign --expiry 24h /@ ``` ### Sign an OCI artifact stored in a registry using a specified signing key ```shell # List signing keys to get the key name notation key list # Sign a container image using the specified key name notation sign --key /@ ``` ### Sign an OCI artifact identified by a tag ```shell # Prerequisites: # A default signing key is configured using CLI "notation key" # Use a tag to identify a container image notation sign /: ``` An example for a successful signing: ```console $ notation sign localhost:5000/net-monitor:v1 Warning: Always sign the artifact using digest(`@sha256:...`) rather than a tag(`:v1`) because tags are mutable and a tag reference can point to a different artifact than the one signed. Successfully signed localhost:5000/net-monitor@sha256:b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9 ``` ### Sign an OCI artifact and timestamp the signature with user specified RFC3161 Timestamp Authority (TSA) ```shell # Prerequisites: # A default signing key is configured using CLI "notation key". # Signer knows the TSA url that they want to use to require a RFC 3161 timestamp. # Signer has downloaded the TSA's root certificate in their file system. # Use option "--timestamp-url" to specify the timestamp authority URL. # Use option "--timestamp-root-cert" to specify the filepath of the tsa root # certificate. notation sign --timestamp-url --timestamp-root-cert /@ ``` ### [Experimental] Sign container images stored in OCI layout directory Container images can be stored in OCI image Layout defined in spec [OCI image layout][oci-image-layout]. It is a directory structure that contains files and folders. The OCI image layout could be a tarball or a directory in the filesystem. For example, a file named `hello-world.tar` or a directory named `hello-world`. Notation only supports signing images stored in OCI layout directory for now. Users can reference an image in the layout using either tags, or the exact digest. For example, use `hello-world:v1` or `hello-world@sha256xxx` to reference the image in OCI layout directory named `hello-world`. Tools like `docker buildx` support building images stored in OCI image layout. The following example creates a tarball named `hello-world.tar` with tag `v1`. Please note that the digest can be retrieved in the output messages of `docker buildx build`. ```shell docker buildx create --use docker buildx build . -f Dockerfile -o type=oci,dest=hello-world.tar -t hello-world:v1 ``` Users need to extract the tarball into a directory first, since Notation only support OCI layout directory for now. The following command creates the OCI layout directory. ```shell mkdir hello-world tar -xf hello-world.tar -C hello-world ``` Use flag `--oci-layout` to sign the image stored in OCI layout directory referenced by `hello-world@sha256xxx`. To access this flag `--oci-layout` , set the environment variable `NOTATION_EXPERIMENTAL=1`. For example: ```shell export NOTATION_EXPERIMENTAL=1 # Assume OCI layout directory hello-world is under current path notation sign --oci-layout hello-world@sha256:xxx ``` Upon successful signing, the signature is stored in the same layout directory and associated with the image. Use `notation list` command to list the signatures, for example: ```shell export NOTATION_EXPERIMENTAL=1 # Assume OCI layout directory hello-world is under current path notation list --oci-layout hello-world@sha256:xxx ``` [oci-image-spec]: https://github.com/opencontainers/image-spec/blob/v1.1.0/spec.md [oci-referers-api]: https://github.com/opencontainers/distribution-spec/blob/v1.1.0/spec.md#listing-referrers [oci-image-layout]: https://github.com/opencontainers/image-spec/blob/v1.1.0/image-layout.md notation-1.2.0/specs/commandline/verify.md000066400000000000000000000346021466375446100206230ustar00rootroot00000000000000# notation verify ## Description Use `notation verify` command to verify signatures associated with artifacts stored in OCI compliant registries. Signature verification succeeds if verification succeeds for at least one of the signatures associated with the artifact. Upon successful verification, the output message is printed out as follows: ```text Successfully verified signature for /@ ``` Tags are mutable and a tag reference can point to a different artifact than that was signed referred by the same tag. If a `tag` is used to identify the OCI artifact, the output message is as follows: ```text Warning: Always verify the artifact using digest(@sha256:...) rather than a tag(:v1) because resolved digest may not point to the same signed artifact, as tags are mutable. Successfully verified signature for /@ ``` A signature can have user defined metadata. If the signature for the OCI artifact contains any metadata, the output message is as follows: ```text Successfully verified signature for /@ The artifact was signed with the following user metadata. KEY VALUE ``` ## Outline ```text Verify signatures associated with the artifact. Usage: notation verify [flags] Flags: -d, --debug debug mode -h, --help help for verify --insecure-registry use HTTP protocol while connecting to registries. Should be used only for testing --max-signatures int maximum number of signatures to evaluate or examine (default 100) --oci-layout [Experimental] verify the artifact stored as OCI image layout -p, --password string password for registry operations (default to $NOTATION_PASSWORD if not specified) --plugin-config stringArray {key}={value} pairs that are passed as it is to a plugin, if the verification is associated with a verification plugin, refer plugin documentation to set appropriate values --scope string [Experimental] set trust policy scope for artifact verification, required and can only be used when flag "--oci-layout" is set -u, --username string username for registry operations (default to $NOTATION_USERNAME if not specified) -m, --user-metadata stringArray user defined {key}={value} pairs that must be present in the signature for successful verification if provided -v, --verbose verbose mode ``` ## Usage Pre-requisite: User needs to configure trust store and trust policy properly before using `notation verify` command. ### Configure Trust Store Use `notation certificate` command to configure trust stores. ### Configure Trust Policy Users who consume signed artifact from a registry use the trust policy to specify trusted identities which sign the artifacts, and level of signature verification to use. The trust policy is a JSON document. User needs to create a file named `trustpolicy.json` under `{NOTATION_CONFIG}`. See [Notation Directory Structure](https://notaryproject.dev/docs/user-guides/how-to/directory-structure/) for `{NOTATION_CONFIG}`. An example of `trustpolicy.json`: ```jsonc { "version": "1.0", "trustPolicies": [ { // Policy for all artifacts, from any registry location. "name": "wabbit-networks-images", // Name of the policy. "registryScopes": [ "localhost:5000/net-monitor" ], // The registry artifacts to which the policy applies. "signatureVerification": { // The level of verification - strict, permissive, audit, skip. "level": "strict" }, "trustStores": [ "ca:wabbit-networks" ], // The trust stores that contains the X.509 trusted roots. "trustedIdentities": [ // Identities that are trusted to sign the artifact. It only includes identities of `ca` and `signingAuthority`. "x509.subject: C=US, ST=WA, L=Seattle, O=wabbit-networks.io, OU=Finance, CN=SecureBuilder" ] } ] } ``` An example of `trustpolicy.json` with RFC 3161 timestamp verification support: ```jsonc { "version": "1.0", "trustPolicies": [ { "name": "wabbit-networks-images", "registryScopes": [ "localhost:5000/net-monitor" ], "signatureVerification": { "level": "strict", "verifyTimestamp": "afterCertExpiry" // Only verify timestamp countersignatures if any code signing certificate has expired. DEFAULT: `always` }, "trustStores": [ "ca:wabbit-networks", "tsa:wabbit-networks-timestamp" ], // To enable timestamp verification, trust store type `tsa` MUST be configured. "trustedIdentities": [ "x509.subject: C=US, ST=WA, L=Seattle, O=wabbit-networks.io, OU=Finance, CN=SecureBuilder" ] } ] } ``` For a Linux user, store file `trustpolicy.json` under directory `${HOME}/.config/notation/`. For a MacOS user, store file `trustpolicy.json` under directory `${HOME}/Library/Application Support/notation/`. For a Windows user, store file `trustpolicy.json` under directory `%USERPROFILE%\AppData\Roaming\notation\`. Example values on trust policy properties: | Property name | Value | Meaning | | ----------------------|--------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------| | name | "wabbit-networks-images" | The name of the policy is "wabbit-networks-images". | | registryScopes | "localhost:5000/net-monitor" | The policy only applies to artifacts stored in repository `localhost:5000/net-monitor`. | | registryScopes | "localhost:5000/net-monitor", "localhost:5000/nginx" | The policy applies to artifacts stored in two repositories: `localhost:5000/net-monitor` and `localhost:5000/nginx`. | | registryScopes | "*" | The policy applies to all the artifacts stored in any repositories. | | signatureVerification | "level": "strict" | Signature verification is performed at strict level, which enforces all validations: `integrity`, `authenticity`, `authentic timestamp`, `expiry` and `revocation`.| | signatureVerification | "level": "permissive" | The permissive level enforces most validations, but will only logs failures for `revocation` and `expiry`. | | signatureVerification | "level": "audit" | The audit level only enforces signature `integrity` if a signature is present. Failure of all other validations are only logged. | | signatureVerification | "level": "skip" | The skip level does not fetch signatures for artifacts and does not perform any signature verification. | | trustStores | "ca:wabbit-networks" | Specify the trust store that uses the format {trust-store-type}:{named-store}. The trust store is added using `notation certificate add` command. | | trustStores | "ca:wabbit-networks", "ca:rocket-networks" | Specify two trust stores, each of which contains the trusted roots against which signatures are verified. | | trustedIdentities | "x509.subject: C=US, ST=WA, L=Seattle, O=wabbit-networks.io, OU=Finance, CN=SecureBuilder" | User only trusts the identity with specific subject. User can use `notation certificate show` command to get the `subject` info. | | trustedIdentities | "*" | User trusts any identity (signing certificate) issued by the CA(s) in trust stores. | User can configure multiple trust policies for different scenarios. See [Trust Policy Schema and properties](https://github.com/notaryproject/notaryproject/blob/v1.1.0-rc.1/specs/trust-store-trust-policy.md#trust-policy) for details. ### Verify signatures on an OCI artifact stored in a registry Configure trust store and trust policy properly before using `notation verify` command. ```shell # Prerequisites: Signatures are stored in a registry referencing the signed OCI artifact # Configure trust store by adding a certificate file into trust store named "wabbit-network" of type "ca" notation certificate add --type ca --store wabbit-networks wabbit-networks.crt # Create a JSON file named "trustpolicy.json" under directory "{NOTATION_CONFIG}". # Verify signatures on the supplied OCI artifact identified by the digest notation verify localhost:5000/net-monitor@sha256:b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9 ``` An example of output messages for a successful verification: ```text Successfully verified signature for localhost:5000/net-monitor@sha256:b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9 ``` ### Verify signatures on an OCI artifact with user metadata Use the `--user-metadata` flag to verify that provided key-value pairs are present in the payload of the valid signature. ```shell # Verify signatures on the supplied OCI artifact identified by the digest and verify that io.wabbit-networks.buildId=123 is present in the signed payload notation verify --user-metadata io.wabbit-networks.buildId=123 localhost:5000/net-monitor@sha256:b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9 ``` An example of output messages for a successful verification: ```text Successfully verified signature for localhost:5000/net-monitor@sha256:b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9 The artifact is signed with the following user metadata. KEY VALUE io.wabbit-networks.buildId 123 ``` An example of output messages for an unsuccessful verification: ```text Error: signature verification failed: unable to find specified metadata in any signatures ``` ### Verify signatures on an OCI artifact identified by a tag A tag is resolved to a digest first before verification. ```shell # Prerequisites: Signatures are stored in a registry referencing the signed OCI artifact # Verify signatures on an OCI artifact identified by the tag notation verify localhost:5000/net-monitor:v1 ``` An example of output messages for a successful verification: ```text Warning: Always verify the artifact using digest(@sha256:...) rather than a tag(:v1) because resolved digest may not point to the same signed artifact, as tags are mutable. Successfully verified signature for localhost:5000/net-monitor@sha256:b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9 ``` ### Verify signatures with RFC 3161 timestamp countersignature on an OCI artifact ```shell # Prerequisites: Configure TSA trust store by adding the root certificate of the trusted TSA into trust store named "wabbit-network-timestamp" of type "tsa" notation certificate add --type tsa --store wabbit-networks-timestamp wabbit-networks-tsa.crt # Verify signatures on an OCI artifact notation verify localhost:5000/net-monitor@sha256:b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9 ``` An example of output messages for a successful verification: ```text Successfully verified signature for localhost:5000/net-monitor@sha256:b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9 ``` ### [Experimental] Verify container images in OCI layout directory Users should configure trust policy properly before verifying artifacts in OCI layout directory. According to trust policy specification, `registryScopes` property of trust policy configuration determines which trust policy is applicable for the given artifact. For example, an image stored in a remote registry is referenced by "localhost:5000/net-monitor:v1". In order to verify the image, the value of `registryScopes` should contain "localhost:5000/net-monitor", which is the repository URL of the image. However, the reference to the image stored in OCI layout directory doesn't contain repository URL information. Users can set `registryScopes` to the URL that the image is supposed to be stored in the registry, and then use flag `--scope` for `notation verify` command to determine which trust policy is used for verification. Here is an example of trust policy configured for image `hello-world:v1`: ```jsonc { "name": "images stored as OCI layout", "registryScopes": [ "local/hello-world" ], "signatureVerification": { "level" : "strict" }, "trustStores": [ "ca:hello-world" ], "trustedIdentities": ["*"] } ``` To verify image `hello-world:v1`, user should set the environment variable `NOTATION_EXPERIMENTAL` and use flags `--oci-layout` and `--scope` together. for example: ```shell export NOTATION_EXPERIMENTAL=1 # Assume OCI layout directory hello-world is under current path # The value of --scope should be set base on the trust policy configuration notation verify --oci-layout --scope "local/hello-world" hello-world:v1 ``` notation-1.2.0/specs/commandline/version.md000066400000000000000000000013221466375446100207750ustar00rootroot00000000000000# notation version ## Description Use `notation version` to print notation version information. Upon successful execution, the following information is printed. ```text Notation - a tool to sign and verify artifacts. Version: Go version: go Git commit: ``` ## Outline ```text Show the notation version information Usage: notation version [flags] Flags: -h, --help Help for version ``` ## Usage ### Print notation version information ```shell notation version ``` An example output: ```text Notation - a tool to sign and verify artifacts. Version: 1.0.0 Go Version: go1.19.2 Git commit: 1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a ``` notation-1.2.0/specs/error-handling-guideline.md000066400000000000000000000135611466375446100217100ustar00rootroot00000000000000# Notation CLI Error Handling and Message Guideline This document aims to provide the guidelines for Notation contributors to improve existing error messages and error handling method as well as the new error output format. It will also provide recommendations and examples for Notation CLI contributors for how to write friendly and standard error messages, avoid generating inconsistent and ambiguous error messages. ## General guiding principles A clear and actionable error message is very important when raising an error, so make sure your error message describes clearly what the error is and tells users what they need to do if possible. First and foremost, make the error messages descriptive and informative. Error messages are expected to be helpful to troubleshoot where the user has done something wrong and the program is guiding them in the right direction. A great error message is recommended to contain the following elements: - HTTP status code: optional, when the logs are generated from the server side, it's recommended to print the HTTP status code in the error description. - Error description: describe what the error is. - Suggestion: for those errors that have potential solution, print out the recommended action. Versioned troubleshooting document link is nice to have. Second, when necessary, it is highly suggested for Notation CLI contributors to provide recommendations for users how to resolve the problems based on the error messages they encountered. Showing descriptive words and straightforward prompt with executable commands as a potential solution is a good practice for error messages. Third, for unhandled errors you didn't expect the user to run into. For that, have a way to view full traceback information as well as full debug or verbose logs output, and instructions on how to submit a bug. Fourth, signal-to-noise ratio is crucial. The more irrelevant output you produce, the longer it's going to take the user to figure out what they did wrong. If your program produces multiple errors of the same type, consider grouping them under a single explanatory header instead of printing many similar-looking lines. Fifth, CLI program termination should follow the standard [Exit Status conventions](https://www.gnu.org/software/libc/manual/html_node/Exit-Status.html) to report execution status information about success or failure. Notation returns `EXIT_FAILURE` if and only if Notation reports one or more errors. Last, error logs can also be useful for post-mortem debugging and can also be written to a file, truncate them occasionally and make sure they don't contain ansi color codes. ## Error output recommendation ### Dos - Provide full description if the user input does not match what Notation CLI expected. A full description should include the actual input received from the user and expected input. - Use the capital letter ahead of each line of any error message. - Print human readable error message. If the error message is mainly from the server and varies by different servers, tell users that the error response is from server. This implies that users may need to contact server side for troubleshooting. - Provide specific and actionable prompt message with argument suggestion or show the example usage for reference. (e.g, Instead of showing flag or argument options is missing, please provide available argument options and guide users to `--help` to view more examples). - If the actionable prompt message is too long to show in the CLI output, consider guide users to Notation user manual or troubleshooting guide with the versioned permanent link. - If the error message is not enough for troubleshooting, guide users to use `--verbose` to print much more detailed logs. - If server returns an error without any [message or detail](https://github.com/opencontainers/distribution-spec/blob/v1.1.0/spec.md#error-codes), consider providing customized error logs to make it clearer. The original server logs can be displayed in debug mode. - As a security tool, `notation` SHOULD prompt users to stop upon verification errors. ### Don'Ts - Do not use a formula-like or a programming expression in the error message. (e.g, `json: cannot unmarshal string into Go value of type map[string]map[string]string.`, or `Parameter 'xyz' must conform to the following pattern: '^[-\\w\\._\\(\\)]+$'`). - Do not use ambiguous expressions which mean nothing to users. (e.g, `Something unexpected happens`, or `Error: accepts 2 arg(s), received 0`). - Do not print irrelevant error message to make the output noisy. The more irrelevant output you produce, the longer it's going to take the user to figure out what they did wrong. - As a security tool, `notation` MUST NOT prompt users to update the trust policy to bypass the verification error when verification fails. - Signing key information is considered sensitive so it's not recommended to print them out in the error logs. ## How to write friendly error message ### Recommended error message structure Here is a sample structure of an error message: ```text {Error|Error response from a remote registry or a server}: {Error description (HTTP status code can be printed out if any)} [Usage: {Command usage}] [{Recommended action}] ``` - HTTP status code is an optional information. Printed out the HTTP status code if the error message is generated from the server side. - Command usage is also an optional information but it's recommended to be printed out when user input doesn't follow the standard usage or examples. - Recommended action (if any) should follow the general guiding principles described above. ## Reference Parts of the content are borrowed from these guidelines: - [Command Line Interface Guidelines](https://clig.dev/#errors) - [ORAS CLI Error Handling Guideline](https://github.com/oras-project/oras/blob/v1.2.0-rc.1/docs/proposals/error-handling-guideline.md) - [12 Factor CLI Apps](https://medium.com/@jdxcode/12-factor-cli-apps-dd3c227a0e46)notation-1.2.0/specs/notation-cli.md000066400000000000000000000046431466375446100174330ustar00rootroot00000000000000# Notation CLI This spec contains reference information on using notation commands. Each command has a reference page along with usages. ## Notation Commands | Command | Description | | ------------------------------------------- | ---------------------------------------------------------------------- | | | [certificate](./commandline/certificate.md) | Manage certificates in trust store | | [inspect](./commandline/inspect.md) | Inspect OCI signatures | | [key](./commandline/key.md) | Manage keys used for signing | | [list](./commandline/list.md) | List signatures of a signed OCI artifact | | [login](./commandline/login.md) | Log into OCI registries | | [logout](./commandline/logout.md) | Log out from the logged in OCI registries | | [plugin](./commandline/plugin.md) | Manage plugins | | [policy](./commandline/policy.md) | Manage trust policy configuration for OCI signature verification | | [sign](./commandline/sign.md) | Sign OCI artifacts | | [verify](./commandline/verify.md) | Verify OCI artifacts | | [version](./commandline/version.md) | Print the version of notation CLI | ## Notation Outline ```text Notation - a tool to sign and verify artifacts Usage: notation [command] Available Commands: certificate Manage certificates in trust store inspect Inspect all signatures associated with a signed OCI artifact key Manage keys used for signing list List signatures of a signed OCI artifact login Log into OCI registries logout Log out from the logged in OCI registries plugin Manage plugins policy Manage trust policy configuration for OCI signature verification sign Sign OCI artifacts verify Verify OCI artifacts version Show the notation version information Flags: -h, --help Help for notation ``` notation-1.2.0/specs/registry-auth.md000066400000000000000000000155051466375446100176410ustar00rootroot00000000000000# Registry Authentication Registry access is required for pulling the manifests of the images to be verified along with their signatures as well as other advanced operations. This documentation specifies how authentication to the remote registry works and how registry credentials are stored. ## Communication Channel Although it is secure to transmit artifacts with their signatures via HTTP connections as a tampered artifact can be detected through signature verification, it is RECOMMENDED to transmit via HTTPS connections for confidentiality and the authenticity of the remote server. Alternatively, clients can be authenticated via mutual TLS authentication (mTLS). In other words, clients connect to servers via HTTP over mTLS by presenting client certificates. In this case, authorization can be applied later without further authentication schemes. ## Authentication Schemes Notation supports [basic HTTP authentication][RFC7617] scheme and token schemes including [OAuth 2.0][RFC6749] and [Docker Registry V2 Token][token]. Clients SHOULD connect to the remote servers via HTTPS if any authentication scheme is applied. ### General Flow ```mermaid flowchart LR; ts[Authorization Service]; client[Notation]; reg[Registry]; client -- 1. Access resource --> reg; reg -- 2. Challenge --> client; client -- 3. Request for token --> ts; ts -- 4. Grant token --> client; client -- 5. Access with token --> reg; reg -- 6. Response --> client; ``` In general, notation clients accesses registry resources as the workflow below, which follows the workflow for [Docker Registry v2 authentication via central service](https://docs.docker.com/registry/spec/auth/token/). 1. Notation attempts to access the remote registry directly. If there is no authentication scheme associated with the registry, skip to *step 6*. 2. The remote registry returns `401 Unauthorized` with a [WWW-Authenticate](https://datatracker.ietf.org/doc/html/rfc7235#section-4.1) challenge, indicating the required authentication scheme. 3. Notation requests the authorization service for a bearer token for accessing the target resource with local credentials. If the remote registry requires *basic* scheme, skip to *step 5*. 4. The authorization grants and returns a bearer token for access back to the notation client. 5. Notation attempts to access the remote registry again with the obtained bearer token (or local credential for *basic* scheme) in the [Authorization](https://datatracker.ietf.org/doc/html/rfc7235#section-4.2) header. 6. The remote registry performs the requested operation and returns the response. Optimization might be performed to reduced the number of requests in order to reduce the overall latency for subsequent requests. ### Basic Scheme Notation follows [RFC 7617][RFC7617] for the *Basic* HTTP authentication scheme. If a remote registry is known to support `Basic` authentication scheme, the attempt-challenge phase (*steps 1-4*) can be skipped, and thus Notation can access the remote registry without overhead in terms of the number of requests. ```mermaid flowchart LR; client[Notation]; reg[Registry]; client -- Access with credentials --> reg; reg -- Response --> client; ``` ### Token Scheme Notation supports two types of token schemes, [Docker][token] and [OAuth 2.0][RFC6749] where OAuth 2.0 is preferred by default. Since token authentication schemes have two more requests per registry requests, notation client implementations SHOULD cache the token to reduce the number of requests. ```mermaid flowchart LR; ts[Authorization Service]; client[Notation]; reg[Registry]; client -- 1. Access resource with cached token --> reg; reg -- 2. Challenge if token invalidated --> client; client -- 3. Request for a new token --> ts; ts -- 4. Grant a new token --> client; client -- 5. Access with a new token --> reg; reg -- 6. Response --> client; ``` The workflow is updated as follows with caching. 1. Notation attempts to access the remote registry using a cached token. If the token is valid, skip to *step 6*. 2. The remote registry returns `401 Unauthorized` with a challenge, requiring a valid token. 3. Notation requests the authorization service for a new bearer token for accessing the target resource with local credentials. 4. The authorization grants and returns a new bearer token for access back to the notation client. The client-side cache is refreshed by the newly returned token. 5. Notation attempts to access the remote registry again. 6. The remote registry performs the requested operation and returns the response. #### Docker In the [Docker Token Authentication][token] specification, the *step 3* is implemented using a `GET` request where users are authenticated by the authorization service via `Basic` authentication scheme. Therefore, user credentials are only accepted in the form of username and password pair. #### OAuth 2 Notation follows the [Docker Registry v2 authentication][oauth2] specification for the [OAuth 2.0][RFC6749] framework where the *step 3* is implemented using a `POST` request. Precisely, notation supports `password` and `refresh_token` grant types. **Note** Refresh tokens are often known as identity tokens in the context of registry authentication. ## Credential Store As local credentials may be required to access the remote registries, they need to be stored and accessed securely. To achieve maximum security, credential helpers are preferred so that credentials are stored in the system key chain with better protection. If credential helpers are not available, credentials SHOULD be provided to notation via command line parameters `--username` / `--password` or environment variables `NOTATION_USERNAME` / `NOTATION_PASSWORD`. ### Credential Helper To achieve maximum compatibility with existing systems, [docker credential helpers](https://github.com/docker/docker-credential-helpers) and its [protocol](https://docs.docker.com/engine/reference/commandline/login/#credential-helper-protocol) are adopted as the credential helpers for `notation`. The credential store can be specified globally or per registry by setting the notation config. ```json { "credHelpers": { "registry.wabbit-networks.io": "wabbithelper", "another.wabbit-networks.io": "foobar" }, "credsStore": "whatever" } ``` [RFC6749]: https://www.rfc-editor.org/rfc/rfc6749 "OAuth 2.0" [RFC7617]: https://www.rfc-editor.org/rfc/rfc7617 "Basic Auth" [token]: https://docs.docker.com/registry/spec/auth/jwt/ "Docker Token Authentication" [oauth2]: https://docs.docker.com/registry/spec/auth/oauth/ "Docker Registry v2 authentication using OAuth2"notation-1.2.0/test/000077500000000000000000000000001466375446100143445ustar00rootroot00000000000000notation-1.2.0/test/e2e/000077500000000000000000000000001466375446100150175ustar00rootroot00000000000000notation-1.2.0/test/e2e/README.md000066400000000000000000000027001466375446100162750ustar00rootroot00000000000000# A Quick Introduction on how Notation end-to-end test works ## Framework Using [Ginkgo](https://onsi.github.io/ginkgo/) as the e2e framework, which is based on the Golang standard testing library. Using [Gomega](https://onsi.github.io/gomega/) as the matching library. ## Introduction ### Data:testdata contains data needed by e2e tests, including: - *config*: notation test key and cert files. - *registry*: OCI layout files and registry config files. ### For developer - *Test registry*: a test registry started before running tests. - *Config isolation*: notation needs a few configuration files in user level directory, which can be isolated by modify `XDG_CONFIG_HOME` environment variable. In Notation E2E test framework, a VirtualHost abstraction is designed for isolating user level configuration. - *Parallelization*: In order to speed up testing, Ginkgo will launch several processes to run e2e test cases. - *Randomization*: By default, Ginkgo will run specs in a suite in random order. Please make sure the test cases can be runned independently. If the test cases depend on the execution order, consider using [Ordered Containers](https://onsi.github.io/ginkgo/#ordered-containers). ## Setting up ### Github Actions - Please check `Run e2e tests` steps in **workflows/build.yml** for detail. ### Local environment - Install Golang. - Install Docker. - Clone the repository. - Run `cd ./test/e2e` - Run `./run.sh ` notation-1.2.0/test/e2e/go.mod000066400000000000000000000020571466375446100161310ustar00rootroot00000000000000module github.com/notaryproject/notation/test/e2e go 1.23 require ( github.com/notaryproject/notation-core-go v1.1.0 github.com/onsi/ginkgo/v2 v2.11.0 github.com/onsi/gomega v1.27.10 github.com/opencontainers/image-spec v1.1.0 oras.land/oras-go/v2 v2.4.0 ) require ( github.com/fxamacker/cbor/v2 v2.7.0 // indirect github.com/go-logr/logr v1.2.4 // indirect github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect github.com/google/go-cmp v0.5.9 // indirect github.com/google/pprof v0.0.0-20230510103437-eeec1cb781c3 // indirect github.com/notaryproject/tspclient-go v0.2.0 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect github.com/veraison/go-cose v1.1.0 // indirect github.com/x448/float16 v0.8.4 // indirect golang.org/x/net v0.23.0 // indirect golang.org/x/sync v0.6.0 // indirect golang.org/x/sys v0.18.0 // indirect golang.org/x/text v0.14.0 // indirect golang.org/x/tools v0.9.3 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) replace github.com/notaryproject/notation/test/e2e/plugin => ./plugin notation-1.2.0/test/e2e/go.sum000066400000000000000000000122011466375446100161460ustar00rootroot00000000000000github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E= github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ= github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/pprof v0.0.0-20230510103437-eeec1cb781c3 h1:2XF1Vzq06X+inNqgJ9tRnGuw+ZVCB3FazXODD6JE1R8= github.com/google/pprof v0.0.0-20230510103437-eeec1cb781c3/go.mod h1:79YE0hCXdHag9sBkw2o+N/YnZtTkXi0UT9Nnixa5eYk= github.com/notaryproject/notation-core-go v1.1.0 h1:xCybcONOKcCyPNihJUSa+jRNsyQFNkrk0eJVVs1kWeg= github.com/notaryproject/notation-core-go v1.1.0/go.mod h1:+6AOh41JPrnVLbW/19SJqdhVHwKgIINBO/np0e7nXJA= github.com/notaryproject/tspclient-go v0.2.0 h1:g/KpQGmyk/h7j60irIRG1mfWnibNOzJ8WhLqAzuiQAQ= github.com/notaryproject/tspclient-go v0.2.0/go.mod h1:LGyA/6Kwd2FlM0uk8Vc5il3j0CddbWSHBj/4kxQDbjs= github.com/onsi/ginkgo/v2 v2.11.0 h1:WgqUCUt/lT6yXoQ8Wef0fsNn5cAuMK7+KT9UFRz2tcU= github.com/onsi/ginkgo/v2 v2.11.0/go.mod h1:ZhrRA5XmEE3x3rhlzamx/JJvujdZoJ2uvgI7kR0iZvM= github.com/onsi/gomega v1.27.10 h1:naR28SdDFlqrG6kScpT8VWpu1xWY5nJRCF3XaYyBjhI= github.com/onsi/gomega v1.27.10/go.mod h1:RsS8tutOdbdgzbPtzzATp12yT7kM5I5aElG3evPbQ0M= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug= github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/veraison/go-cose v1.1.0 h1:AalPS4VGiKavpAzIlBjrn7bhqXiXi4jbMYY/2+UC+4o= github.com/veraison/go-cose v1.1.0/go.mod h1:7ziE85vSq4ScFTg6wyoMXjucIGOf4JkFEZi/an96Ct4= github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw= golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54= golang.org/x/mod v0.10.0 h1:lFO9qtOdlre5W1jxS3r/4szv2/6iXxScdzjoBMXNhYk= golang.org/x/mod v0.10.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs= golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/tools v0.9.3 h1:Gn1I8+64MsuTb/HpH+LmQtNas23LhUVr3rYZ0eKuaMM= golang.org/x/tools v0.9.3/go.mod h1:owI94Op576fPu3cIGQeHs3joujW/2Oc6MtlxbF5dfNc= google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw= google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= oras.land/oras-go/v2 v2.4.0 h1:i+Wt5oCaMHu99guBD0yuBjdLvX7Lz8ukPbwXdR7uBMs= oras.land/oras-go/v2 v2.4.0/go.mod h1:osvtg0/ClRq1KkydMAEu/IxFieyjItcsQ4ut4PPF+f8= notation-1.2.0/test/e2e/internal/000077500000000000000000000000001466375446100166335ustar00rootroot00000000000000notation-1.2.0/test/e2e/internal/notation/000077500000000000000000000000001466375446100204665ustar00rootroot00000000000000notation-1.2.0/test/e2e/internal/notation/file.go000066400000000000000000000025021466375446100217330ustar00rootroot00000000000000// Copyright The Notary Project Authors. // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package notation import ( "encoding/json" "io" "os" ) // copyFile copies the source file to the destination file func copyFile(src, dst string) error { in, err := os.Open(src) if err != nil { return err } defer in.Close() si, err := in.Stat() if err != nil { return err } out, err := os.Create(dst) if err != nil { return err } defer out.Close() if _, err := io.Copy(out, in); err != nil { return err } if err := out.Sync(); err != nil { return err } return out.Chmod(si.Mode()) } // saveJSON marshals the data and save to the given path. func saveJSON(data any, path string) error { f, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE, 0600) if err != nil { return err } return json.NewEncoder(f).Encode(data) } notation-1.2.0/test/e2e/internal/notation/host.go000066400000000000000000000215041466375446100217740ustar00rootroot00000000000000// Copyright The Notary Project Authors. // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package notation import ( "os" "path/filepath" "github.com/notaryproject/notation/test/e2e/internal/utils" . "github.com/onsi/ginkgo/v2" ) // CoreTestFunc is the test function running in a VirtualHost. // // notation is an Executor isolated by $XDG_CONFIG_HOME. // artifact is a generated artifact in a new repository. // vhost is the VirtualHost instance. type CoreTestFunc func(notation *utils.ExecOpts, artifact *Artifact, vhost *utils.VirtualHost) // OCILayoutTestFunc is the test function running in a VirtualHost with isolated // OCI layout for each test case. // // notation is an Executor isolated by $XDG_CONFIG_HOME. // vhost is the VirtualHost instance. type OCILayoutTestFunc func(notation *utils.ExecOpts, ocilayout *OCILayout, vhost *utils.VirtualHost) // Host creates a virtualized notation testing host by modify // the "XDG_CONFIG_HOME" environment variable of the Executor. // // options is the required testing environment options // fn is the callback function containing the testing logic. func Host(options []utils.HostOption, fn CoreTestFunc) { // create a notation vhost vhost, err := createNotationHost(NotationBinPath, options...) if err != nil { panic(err) } // generate a repository with an artifact artifact := GenerateArtifact("", "") // run the main logic fn(vhost.Executor, artifact, vhost) } // HostInGithubAction only run the test in GitHub Actions. // // The booting script will setup TLS reverse proxy and TLS certificate // for Github Actions environment. func HostInGithubAction(options []utils.HostOption, fn CoreTestFunc) { if os.Getenv("GITHUB_ACTIONS") != "true" { Skip("only run in GitHub Actions") } Host(options, fn) } // HostWithOCILayout creates a virtualized notation testing host by modify // the "XDG_CONFIG_HOME" environment variable of the Executor. It generates // isolated OCI layout in the testing host. // // options is the required testing environment options // fn is the callback function containing the testing logic. func HostWithOCILayout(options []utils.HostOption, fn OCILayoutTestFunc) { // create a notation vhost vhost, err := createNotationHost(NotationBinPath, options...) if err != nil { panic(err) } ocilayout, err := GenerateOCILayout("") if err != nil { panic(err) } // run the main logic fn(vhost.Executor, ocilayout, vhost) } // OldNotation create an old version notation ExecOpts in a VirtualHost // for testing forward compatibility. func OldNotation(options ...utils.HostOption) *utils.ExecOpts { if len(options) == 0 { options = BaseOptions() } vhost, err := createNotationHost(NotationOldBinPath, options...) if err != nil { panic(err) } return vhost.Executor } func createNotationHost(path string, options ...utils.HostOption) (*utils.VirtualHost, error) { vhost, err := utils.NewVirtualHost(path, CreateNotationDirOption()) if err != nil { return nil, err } // set additional options vhost.SetOption(options...) return vhost, nil } // Opts is a grammar sugar to generate a list of HostOption. func Opts(options ...utils.HostOption) []utils.HostOption { return options } // BaseOptions returns a list of base Options for a valid notation. // testing environment. func BaseOptions() []utils.HostOption { return Opts( AuthOption("", ""), AddKeyOption("e2e.key", "e2e.crt"), AddTrustStoreOption("e2e", filepath.Join(NotationE2ELocalKeysDir, "e2e.crt")), AddTrustPolicyOption("trustpolicy.json"), ) } // TimestampOptions returns a list of timestamp Options for a valid // notation testing environment. func TimestampOptions(verifyTimestamp string) []utils.HostOption { var trustPolicyOption utils.HostOption if verifyTimestamp == "afterCertExpiry" { trustPolicyOption = AddTrustPolicyOption("timestamp_after_cert_expiry_trustpolicy.json") } else { trustPolicyOption = AddTrustPolicyOption("timestamp_trustpolicy.json") } return Opts( AuthOption("", ""), AddKeyOption("e2e.key", "e2e.crt"), AddTrustStoreOption("e2e", filepath.Join(NotationE2ELocalKeysDir, "e2e.crt")), AddTimestampTrustStoreOption("e2e", filepath.Join(NotationE2EConfigPath, "timestamp", "globalsignTSARoot.cer")), AddTimestampTrustStoreOption("e2e", filepath.Join(NotationE2EConfigPath, "timestamp", "DigiCertTSARootSHA384.cer")), trustPolicyOption, ) } func BaseOptionsWithExperimental() []utils.HostOption { return Opts( AuthOption("", ""), AddKeyOption("e2e.key", "e2e.crt"), AddTrustStoreOption("e2e", filepath.Join(NotationE2ELocalKeysDir, "e2e.crt")), AddTrustPolicyOption("trustpolicy.json"), EnableExperimental(), ) } // TestLoginOptions returns the BaseOptions with removing AuthOption and adding ConfigOption. // testing environment. func TestLoginOptions() []utils.HostOption { return Opts( AddKeyOption("e2e.key", "e2e.crt"), AddTrustStoreOption("e2e", filepath.Join(NotationE2ELocalKeysDir, "e2e.crt")), AddTrustPolicyOption("trustpolicy.json"), AddConfigJsonOption("pass_credential_helper_config.json"), ) } // CreateNotationDirOption creates the notation directory in temp user dir. func CreateNotationDirOption() utils.HostOption { return func(vhost *utils.VirtualHost) error { return os.MkdirAll(vhost.AbsolutePath(NotationDirName), os.ModePerm) } } // AuthOption sets the auth environment variables for notation. func AuthOption(username, password string) utils.HostOption { if username == "" { username = TestRegistry.Username } if password == "" { password = TestRegistry.Password } return func(vhost *utils.VirtualHost) error { vhost.UpdateEnv(authEnv(username, password)) return nil } } // AddKeyOption adds the test signingkeys.json, key and cert files to // the notation directory. func AddKeyOption(keyName, certName string) utils.HostOption { return func(vhost *utils.VirtualHost) error { return AddKeyPairs(vhost.AbsolutePath(NotationDirName), keyName, certName) } } // AddTrustStoreOption adds the test cert to the trust store. func AddTrustStoreOption(namedstore string, srcCertPath string) utils.HostOption { return func(vhost *utils.VirtualHost) error { vhost.Executor. Exec("cert", "add", "--type", "ca", "--store", namedstore, srcCertPath). MatchKeyWords("Successfully added following certificates") return nil } } // AddTimestampTrustStoreOption adds the test tsa cert to the trust store. func AddTimestampTrustStoreOption(namedstore string, srcCertPath string) utils.HostOption { return func(vhost *utils.VirtualHost) error { vhost.Executor. Exec("cert", "add", "--type", "tsa", "--store", namedstore, srcCertPath). MatchKeyWords("Successfully added following certificates") return nil } } // AddTrustPolicyOption adds a valid trust policy for testing. func AddTrustPolicyOption(trustpolicyName string) utils.HostOption { return func(vhost *utils.VirtualHost) error { return copyFile( filepath.Join(NotationE2ETrustPolicyDir, trustpolicyName), vhost.AbsolutePath(NotationDirName, TrustPolicyName), ) } } // AddConfigJsonOption adds a valid config.json for testing. func AddConfigJsonOption(configJsonName string) utils.HostOption { return func(vhost *utils.VirtualHost) error { return copyFile( filepath.Join(NotationE2EConfigJsonDir, configJsonName), vhost.AbsolutePath(NotationDirName, ConfigJsonName), ) } } // AddPlugin adds a pluginkeys.json config file and installs an e2e-plugin. func AddPlugin(pluginPath string) utils.HostOption { return func(vhost *utils.VirtualHost) error { // add pluginkeys.json configuration file for e2e-plugin saveJSON( generatePluginKeys(vhost.AbsolutePath(NotationDirName)), vhost.AbsolutePath(NotationDirName, "pluginkeys.json"), ) // install plugin e2ePluginDir := vhost.AbsolutePath(NotationDirName, PluginDirName, PluginName) if err := os.MkdirAll(e2ePluginDir, 0700); err != nil { return err } return copyFile( NotationE2EPluginPath, filepath.Join(e2ePluginDir, "notation-"+PluginName), ) } } // authEnv creates an auth info. // (By setting $NOTATION_USERNAME and $NOTATION_PASSWORD) func authEnv(username, password string) map[string]string { return map[string]string{ "NOTATION_USERNAME": username, "NOTATION_PASSWORD": password, } } // EnableExperimental enables experimental features. func EnableExperimental() utils.HostOption { return func(vhost *utils.VirtualHost) error { vhost.UpdateEnv(map[string]string{"NOTATION_EXPERIMENTAL": "1"}) return nil } } notation-1.2.0/test/e2e/internal/notation/init.go000066400000000000000000000102421466375446100217570ustar00rootroot00000000000000// Copyright The Notary Project Authors. // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package notation import ( "fmt" "os" "path/filepath" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" ) const ( NotationDirName = "notation" TrustPolicyName = "trustpolicy.json" TrustStoreDirName = "truststore" TrustStoreTypeCA = "ca" PluginDirName = "plugins" PluginName = "e2e-plugin" ConfigJsonName = "config.json" ) const ( envKeyRegistryHost = "NOTATION_E2E_REGISTRY_HOST" envKeyRegistryUsername = "NOTATION_E2E_REGISTRY_USERNAME" envKeyRegistryPassword = "NOTATION_E2E_REGISTRY_PASSWORD" envKeyDomainRegistryHost = "NOTATION_E2E_DOMAIN_REGISTRY_HOST" envKeyNotationBinPath = "NOTATION_E2E_BINARY_PATH" envKeyNotationOldBinPath = "NOTATION_E2E_OLD_BINARY_PATH" envKeyNotationPluginPath = "NOTATION_E2E_PLUGIN_PATH" envKeyNotationPluginTarGzPath = "NOTATION_E2E_PLUGIN_TAR_GZ_PATH" envKeyNotationMaliciouPluginArchivePath = "NOTATION_E2E_MALICIOUS_PLUGIN_ARCHIVE_PATH" envKeyNotationConfigPath = "NOTATION_E2E_CONFIG_PATH" envKeyOCILayoutPath = "NOTATION_E2E_OCI_LAYOUT_PATH" envKeyTestRepo = "NOTATION_E2E_TEST_REPO" envKeyTestTag = "NOTATION_E2E_TEST_TAG" ) var ( // NotationBinPath is the notation binary path. NotationBinPath string // NotationOldBinPath is the path of an old version notation binary for // testing forward compatibility. NotationOldBinPath string NotationE2EPluginPath string NotationE2EPluginTarGzPath string NotationE2EMaliciousPluginArchivePath string NotationE2EConfigPath string NotationE2ELocalKeysDir string NotationE2ETrustPolicyDir string NotationE2EConfigJsonDir string ) var ( OCILayoutPath string TestRepoUri string TestTag string RegistryStoragePath string ) func init() { RegisterFailHandler(Fail) setUpRegistry() setUpNotationValues() } func setUpRegistry() { setValue(envKeyRegistryHost, &TestRegistry.Host) setValue(envKeyRegistryUsername, &TestRegistry.Username) setValue(envKeyRegistryPassword, &TestRegistry.Password) setValue(envKeyDomainRegistryHost, &TestRegistry.DomainHost) setPathValue(envKeyOCILayoutPath, &OCILayoutPath) setValue(envKeyTestRepo, &TestRepoUri) setValue(envKeyTestTag, &TestTag) } func setUpNotationValues() { // set Notation binary path setPathValue(envKeyNotationBinPath, &NotationBinPath) setPathValue(envKeyNotationOldBinPath, &NotationOldBinPath) // set Notation e2e-plugin path setPathValue(envKeyNotationPluginPath, &NotationE2EPluginPath) setPathValue(envKeyNotationPluginTarGzPath, &NotationE2EPluginTarGzPath) setPathValue(envKeyNotationMaliciouPluginArchivePath, &NotationE2EMaliciousPluginArchivePath) // set Notation configuration paths setPathValue(envKeyNotationConfigPath, &NotationE2EConfigPath) NotationE2ETrustPolicyDir = filepath.Join(NotationE2EConfigPath, "trustpolicies") NotationE2ELocalKeysDir = filepath.Join(NotationE2EConfigPath, LocalKeysDirName) NotationE2EConfigJsonDir = filepath.Join(NotationE2EConfigPath, LocalConfigJsonsDirName) } func setPathValue(envKey string, value *string) { setValue(envKey, value) if !filepath.IsAbs(*value) { panic(fmt.Sprintf("env %s=%q is not a absolute path", envKey, *value)) } } func setValue(envKey string, value *string) { if *value = os.Getenv(envKey); *value == "" { panic(fmt.Sprintf("env %s is empty", envKey)) } fmt.Printf("set test value $%s=%s\n", envKey, *value) } notation-1.2.0/test/e2e/internal/notation/key.go000066400000000000000000000060531466375446100216110ustar00rootroot00000000000000// Copyright The Notary Project Authors. // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package notation import ( "os" "path/filepath" ) const ( SigningKeysFileName = "signingkeys.json" LocalKeysDirName = "localkeys" LocalConfigJsonsDirName = "configjsons" ) // X509KeyPair contains the paths of a public/private key pair files. type X509KeyPair struct { KeyPath string `json:"keyPath"` CertificatePath string `json:"certPath"` } // ExternalKey contains the necessary information to delegate // the signing operation to the named plugin. type ExternalKey struct { ID string `json:"id,omitempty"` PluginName string `json:"pluginName,omitempty"` PluginConfig map[string]string `json:"pluginConfig,omitempty"` } // KeySuite is a named key suite. type KeySuite struct { Name string `json:"name"` *X509KeyPair *ExternalKey } // SigningKeys reflects the signingkeys.json file. type SigningKeys struct { Default string `json:"default"` Keys []KeySuite `json:"keys"` } // AddKeyPairs creates the signingkeys.json file and the localkeys directory // with e2e.key and e2e.crt func AddKeyPairs(dir, keyName, certName string) error { // create signingkeys.json files if err := saveJSON( generateSigningKeys(dir), filepath.Join(dir, SigningKeysFileName)); err != nil { return err } // create localkeys directory localKeysDir := filepath.Join(dir, LocalKeysDirName) os.MkdirAll(localKeysDir, 0700) // copy key and cert files if err := copyFile(filepath.Join(NotationE2ELocalKeysDir, keyName), filepath.Join(localKeysDir, "e2e.key")); err != nil { return err } return copyFile(filepath.Join(NotationE2ELocalKeysDir, certName), filepath.Join(localKeysDir, "e2e.crt")) } // generateSigningKeys generates the signingkeys.json for notation. func generateSigningKeys(dir string) *SigningKeys { return &SigningKeys{ Default: "e2e", Keys: []KeySuite{ { Name: "e2e", X509KeyPair: &X509KeyPair{ KeyPath: filepath.Join(dir, "localkeys", "e2e.key"), CertificatePath: filepath.Join(dir, "localkeys", "e2e.crt"), }, }, }, } } // generatePluginKeys generates pluginkeys.json for e2e-plugin. func generatePluginKeys(dir string) *SigningKeys { return &SigningKeys{ Keys: []KeySuite{ { Name: "e2e-plugin", X509KeyPair: &X509KeyPair{ KeyPath: filepath.Join(dir, "localkeys", "e2e.key"), CertificatePath: filepath.Join(dir, "localkeys", "e2e.crt"), }, ExternalKey: &ExternalKey{ ID: "key1", PluginName: PluginName, }, }, }, } } notation-1.2.0/test/e2e/internal/notation/layout.go000066400000000000000000000041471466375446100223400ustar00rootroot00000000000000// Copyright The Notary Project Authors. // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package notation import ( "context" "os" "path/filepath" . "github.com/onsi/ginkgo/v2" "oras.land/oras-go/v2" "oras.land/oras-go/v2/content/oci" ) // OCILayout is a OCI layout directory for type OCILayout struct { // Path is the path of the OCI layout directory. Path string // Tag is the tag of artifact in the OCI layout. Tag string // Digest is the digest of artifact in the OCI layout. Digest string } // GenerateOCILayout creates a new OCI layout in a temporary directory. func GenerateOCILayout(srcRepoName string) (*OCILayout, error) { ctx := context.Background() if srcRepoName == "" { srcRepoName = TestRepoUri } destPath := filepath.Join(GinkgoT().TempDir(), newRepoName()) // create a local store from OCI layout directory. srcStore, err := oci.NewFromFS(ctx, os.DirFS(filepath.Join(OCILayoutPath, srcRepoName))) if err != nil { return nil, err } // create a dest store for store the generated oci layout. destStore, err := oci.New(destPath) if err != nil { return nil, err } // copy data desc, err := oras.ExtendedCopy(ctx, srcStore, TestTag, destStore, "", oras.DefaultExtendedCopyOptions) if err != nil { return nil, err } return &OCILayout{ Path: destPath, Tag: TestTag, Digest: desc.Digest.String(), }, nil } // ReferenceWithTag returns the reference with tag. func (o *OCILayout) ReferenceWithTag() string { return o.Path + ":" + o.Tag } // ReferenceWithDigest returns the reference with digest. func (o *OCILayout) ReferenceWithDigest() string { return o.Path + "@" + o.Digest } notation-1.2.0/test/e2e/internal/notation/registry.go000066400000000000000000000127241466375446100226730ustar00rootroot00000000000000// Copyright The Notary Project Authors. // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package notation import ( "context" "fmt" "hash/maphash" "net" "os" "path/filepath" ocispec "github.com/opencontainers/image-spec/specs-go/v1" "oras.land/oras-go/v2" "oras.land/oras-go/v2/content/oci" "oras.land/oras-go/v2/registry" "oras.land/oras-go/v2/registry/remote" "oras.land/oras-go/v2/registry/remote/auth" ) const ArtifactTypeNotation = "application/vnd.cncf.notary.signature" type Registry struct { // Host is the registry host. Host string // Username is the username to access the registry. Username string // Password is the password to access the registry. Password string // DomainHost is a registry host, separate from localhost, used for testing // the --insecure-registry flag. // // If the host is localhost, Notation connects via plain HTTP. For // non-localhost hosts, Notation defaults to HTTPS. However, users can // enforce HTTP by setting the --insecure-registry flag. DomainHost string } // CreateArtifact copies a local OCI layout to the registry to create // a new artifact with a new repository. // // srcRepoName is the repo name in ./testdata/registry/oci_layout folder. // destRepoName is the repo name to be created in the registry. func (r *Registry) CreateArtifact(srcRepoName, destRepoName string) (*Artifact, error) { ctx := context.Background() // create a local store from OCI layout directory. srcStore, err := oci.NewFromFS(ctx, os.DirFS(filepath.Join(OCILayoutPath, srcRepoName))) if err != nil { return nil, err } // create the artifact struct artifact := &Artifact{ Registry: r, Repo: destRepoName, Tag: TestTag, } // create the remote.repository destRepo, err := newRepository(artifact.ReferenceWithTag()) if err != nil { return nil, err } // copy data desc, err := oras.ExtendedCopy(ctx, srcStore, artifact.Tag, destRepo, "", oras.DefaultExtendedCopyOptions) if err != nil { return nil, err } artifact.Digest = desc.Digest.String() return artifact, err } var TestRegistry = Registry{} // Artifact describes an artifact in a repository. type Artifact struct { *Registry // Repo is the repository name. Repo string // Tag is the tag of the artifact. Tag string // Digest is the digest of the artifact. Digest string } // GenerateArtifact generates a new artifact with a new repository by copying // the source repository in the OCILayoutPath to be a new repository. func GenerateArtifact(srcRepo, newRepo string) *Artifact { if srcRepo == "" { srcRepo = TestRepoUri } if newRepo == "" { // generate new repo newRepo = newRepoName() } artifact, err := TestRegistry.CreateArtifact(srcRepo, newRepo) if err != nil { panic(err) } return artifact } // ReferenceWithTag returns the /: func (r *Artifact) ReferenceWithTag() string { return fmt.Sprintf("%s/%s:%s", r.Host, r.Repo, r.Tag) } // ReferenceWithDigest returns the /@: func (r *Artifact) ReferenceWithDigest() string { return fmt.Sprintf("%s/%s@%s", r.Host, r.Repo, r.Digest) } // DomainReferenceWithDigest returns the /@: // for testing --insecure-registry flag and TLS request. func (r *Artifact) DomainReferenceWithDigest() string { return fmt.Sprintf("%s/%s@%s", r.DomainHost, r.Repo, r.Digest) } // SignatureManifest returns the manifest of the artifact. func (r *Artifact) SignatureDescriptors() ([]ocispec.Descriptor, error) { ctx := context.Background() repo, err := newRepository(r.ReferenceWithDigest()) if err != nil { return nil, err } // get manifest descriptor desc, err := repo.Manifests().Resolve(ctx, r.ReferenceWithDigest()) if err != nil { return nil, err } // get signature descriptors var descriptors []ocispec.Descriptor if err := repo.Referrers(context.Background(), desc, ArtifactTypeNotation, func(referrers []ocispec.Descriptor) error { descriptors = append(descriptors, referrers...) return nil }); err != nil { return nil, err } return descriptors, nil } func newRepoName() string { var newRepo string seed := maphash.MakeSeed() newRepo = fmt.Sprintf("%s-%d", TestRepoUri, maphash.Bytes(seed, nil)) return newRepo } func newRepository(reference string) (*remote.Repository, error) { ref, err := registry.ParseReference(reference) if err != nil { return nil, err } repo := &remote.Repository{ Client: authClient(ref), Reference: ref, PlainHTTP: false, } if host, _, _ := net.SplitHostPort(ref.Host()); host == "localhost" { repo.PlainHTTP = true } return repo, nil } func authClient(ref registry.Reference) *auth.Client { return &auth.Client{ Credential: func(ctx context.Context, registry string) (auth.Credential, error) { switch registry { case ref.Host(): return auth.Credential{ Username: TestRegistry.Username, Password: TestRegistry.Password, }, nil default: return auth.EmptyCredential, nil } }, Cache: auth.NewCache(), ClientID: "notation", } } notation-1.2.0/test/e2e/internal/utils/000077500000000000000000000000001466375446100177735ustar00rootroot00000000000000notation-1.2.0/test/e2e/internal/utils/exec.go000066400000000000000000000115521466375446100212520ustar00rootroot00000000000000// Copyright The Notary Project Authors. // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // copied and adopted from https://github.com/oras-project/oras with // modification /* Copyright The ORAS Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package utils import ( "fmt" "io" "os" "os/exec" "strings" "time" "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "github.com/onsi/gomega/gexec" ) const ( DefaultTimeout = 10 * time.Second // If the command hasn't exited yet, ginkgo session ExitCode is -1 notResponding = -1 ) // ExecOpts is an option used to execute a command. type ExecOpts struct { binPath string workDir string timeout time.Duration stdin io.Reader exitCode int text string // env is the environment variables used by the command. env map[string]string } // Binary returns default execution option for customized binary. func Binary(binPath string) *ExecOpts { return &ExecOpts{ binPath: binPath, timeout: DefaultTimeout, exitCode: 0, env: make(map[string]string), } } // ExpectFailure sets failure exit code checking for the execution. func (opts *ExecOpts) ExpectFailure() *ExecOpts { // set to 1 but only check if it's positive opts.exitCode = 1 return opts } // ExpectBlocking consistently check if the execution is blocked. func (opts *ExecOpts) ExpectBlocking() *ExecOpts { opts.exitCode = notResponding return opts } // WithTimeOut sets timeout for the execution. func (opts *ExecOpts) WithTimeOut(timeout time.Duration) *ExecOpts { opts.timeout = timeout return opts } // WithDescription sets description text for the execution. func (opts *ExecOpts) WithDescription(text string) *ExecOpts { opts.text = text return opts } // WithWorkDir sets working directory for the execution. func (opts *ExecOpts) WithWorkDir(path string) *ExecOpts { opts.workDir = path return opts } // WithInput redirects stdin to r for the execution. func (opts *ExecOpts) WithInput(r io.Reader) *ExecOpts { opts.stdin = r return opts } // WithEnv update the environment variables. func (opts *ExecOpts) WithEnv(env map[string]string) *ExecOpts { if env == nil { return opts } if opts.env == nil { opts.env = make(map[string]string) } for key, value := range env { opts.env[key] = value } return opts } // Exec run the execution based on opts. func (opts *ExecOpts) Exec(args ...string) *Matcher { if opts == nil { // this should be a code error but can only be caught during runtime panic("Nil option for command execution") } if opts.text == "" { // set default description text switch opts.exitCode { case notResponding: opts.text = "block" case 0: opts.text = "pass" default: opts.text = "fail" } } description := fmt.Sprintf("\n>> should %s: %s %s >>", opts.text, opts.binPath, strings.Join(args, " ")) ginkgo.By(description) var cmd *exec.Cmd cmd = exec.Command(opts.binPath, args...) // set environment variables cmd.Env = append(cmd.Env, os.Environ()...) for key, val := range opts.env { cmd.Env = append(cmd.Env, fmt.Sprintf("%v=%v", key, val)) } // set stdin cmd.Stdin = opts.stdin if opts.workDir != "" { // switch working directory wd, err := os.Getwd() Expect(err).ShouldNot(HaveOccurred()) Expect(os.Chdir(opts.workDir)).ShouldNot(HaveOccurred()) defer os.Chdir(wd) } fmt.Println(description) session, err := gexec.Start(cmd, os.Stdout, os.Stderr) Expect(err).ShouldNot(HaveOccurred()) if opts.exitCode == notResponding { Consistently(session.ExitCode).WithTimeout(opts.timeout).Should(Equal(notResponding)) session.Kill() } else { exitCode := session.Wait(opts.timeout).ExitCode() Expect(opts.exitCode == 0).To(Equal(exitCode == 0)) } // clear ExecOpts state opts.Clear() return NewMatcher(session) } // Clear clears the ExecOpts to get ready for the next execution. func (opts *ExecOpts) Clear() { opts.exitCode = 0 opts.timeout = DefaultTimeout opts.workDir = "" opts.stdin = nil opts.text = "" } notation-1.2.0/test/e2e/internal/utils/host.go000066400000000000000000000046731466375446100213110ustar00rootroot00000000000000// Copyright The Notary Project Authors. // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package utils import ( "path/filepath" "github.com/onsi/ginkgo/v2" ) // VirtualHost is a virtualized host machine isolated by environment variable. type VirtualHost struct { Executor *ExecOpts userDir string env map[string]string } // NewVirtualHost creates a temporary user-level directory and updates // the "XDG_CONFIG_HOME" environment variable for Executor of the VirtualHost. func NewVirtualHost(binPath string, options ...HostOption) (*VirtualHost, error) { vhost := &VirtualHost{ Executor: Binary(binPath), } // setup a temp user directory vhost.userDir = ginkgo.GinkgoT().TempDir() // set user dir environment variables vhost.UpdateEnv(UserConfigEnv(vhost.userDir)) // set options vhost.SetOption(options...) return vhost, nil } // AbsolutePath returns the absolute path for the given path // elements that are relative to the user directory. func (h *VirtualHost) AbsolutePath(elem ...string) string { userElem := []string{h.userDir} userElem = append(userElem, elem...) return filepath.Join(userElem...) } // UpdateEnv updates the environment variables for the VirtualHost. func (h *VirtualHost) UpdateEnv(env map[string]string) { if h.env == nil { h.env = make(map[string]string) } for key, value := range env { h.env[key] = value } // update ExecOpts.env h.Executor.WithEnv(h.env) } // SetOption sets the options for the host. func (h *VirtualHost) SetOption(options ...HostOption) { for _, option := range options { if err := option(h); err != nil { panic(err) } } } // HostOption is a function to set the host configuration. type HostOption func(vhost *VirtualHost) error // UserConfigEnv creates environment variable for changing // user config dir (By setting $XDG_CONFIG_HOME). func UserConfigEnv(dir string) map[string]string { // create and set user dir for linux return map[string]string{ "XDG_CONFIG_HOME": dir, } } notation-1.2.0/test/e2e/internal/utils/matcher.go000066400000000000000000000047301466375446100217510ustar00rootroot00000000000000// Copyright The Notary Project Authors. // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package utils import ( "fmt" "strings" . "github.com/onsi/gomega" "github.com/onsi/gomega/format" "github.com/onsi/gomega/gexec" ) func init() { // expand the length limit for the gomega matcher format.MaxLength = 1000000 } // Matcher contains the execution result for matching. type Matcher struct { Session *gexec.Session stdout string stderr string } // NewMatcher returns a new Matcher. func NewMatcher(session *gexec.Session) *Matcher { return &Matcher{ Session: session, stdout: string(session.Out.Contents()), stderr: string(session.Err.Contents()), } } // MatchContent matches the content with the stdout. func (m *Matcher) MatchContent(content string) *Matcher { Expect(m.stdout).Should(Equal(content)) return m } // MatchErrContent matches the content with stderr. func (m *Matcher) MatchErrContent(content string) *Matcher { Expect(m.stderr).Should(Equal(content)) return m } // MatchKeyWords matches given keywords with the stdout. func (m *Matcher) MatchKeyWords(keywords ...string) *Matcher { matchKeyWords(m.stdout, keywords) return m } // MatchErrKeyWords matches given keywords with the stderr. func (m *Matcher) MatchErrKeyWords(keywords ...string) *Matcher { matchKeyWords(m.stderr, keywords) return m } // NoMatchErrKeyWords guarantees that the given keywords do not match with // the stderr. func (m *Matcher) NoMatchErrKeyWords(keywords ...string) *Matcher { for _, w := range keywords { Expect(m.stderr).ShouldNot(ContainSubstring(w)) } return m } // MatchErrKeyWords matches given keywords with the stderr. func matchKeyWords(content string, keywords []string) { var missed []string lowered := strings.ToLower(content) for _, w := range keywords { if !strings.Contains(lowered, strings.ToLower(w)) { missed = append(missed, w) } } if len(missed) != 0 { fmt.Printf("Keywords missed: %v\n", missed) panic("failed to match all keywords") } } notation-1.2.0/test/e2e/internal/utils/validator/000077500000000000000000000000001466375446100217605ustar00rootroot00000000000000notation-1.2.0/test/e2e/internal/utils/validator/validator.go000066400000000000000000000017151466375446100243000ustar00rootroot00000000000000// Copyright The Notary Project Authors. // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package validator import ( "os" . "github.com/onsi/gomega" ) // CheckFileExist checks file exists. func CheckFileExist(f string) { _, err := os.Stat(f) Expect(err).ShouldNot(HaveOccurred()) } // CheckFileNotExist checks file not exist. func CheckFileNotExist(f string) { _, err := os.Stat(f) Expect(err).Should(HaveOccurred()) Expect(os.IsNotExist(err)).To(BeTrue()) } notation-1.2.0/test/e2e/plugin/000077500000000000000000000000001466375446100163155ustar00rootroot00000000000000notation-1.2.0/test/e2e/plugin/README.md000066400000000000000000000025721466375446100176020ustar00rootroot00000000000000# Notation E2E Plugin The package implement a simple plugin for Notation plugin extensibility E2E test. ## Commands It support following commands: - get-plugin-metadata (the plugin information) - describe-key (the keySpec) - generate-signature (signing a payload with protected data) - generate-envelope (signing a payload with a descriptor and generate the envelope) - verify-signature (fake verifier for testing the plugin protocol) the `SIGNATURE_GENERATOR.RAW` and `SIGNATURE_GENERATOR.ENVELOPE` capabilities are hidden by default. `SIGNATURE_VERIFIER.TRUSTED_IDENTITY` and `SIGNATURE_VERIFIER.REVOCATION_CHECK` are shown all the time in the response of `get-plugin-metadata` command. You can enable one of signing capability by passing the enable flag in PluginConfig for `get-plugin-metadata` command: ```json { "pluginConfig": { "SIGNATURE_GENERATOR.RAW": "true" } } ``` > Notation only uses one of `SIGNATURE_GENERATOR.RAW` or `SIGNATURE_GENERATOR.ENVELOPE` capability for a signing operation. ## Config It reads the $NOTATION_CONFIG_DIR/pluginkeys.json to get the key and cert info. example pluginkey.json file ```json { "keys": [ { "name": "e2e", "id": "keyid", "keyPath": "/home/username/.config/notation/localkeys/e2e.key", "certPath": "/home/username/.config/notation/localkeys/e2e.crt" } ] } ```notation-1.2.0/test/e2e/plugin/describe_key.go000066400000000000000000000066201466375446100213000ustar00rootroot00000000000000// Copyright The Notary Project Authors. // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package main import ( "crypto" "crypto/tls" "crypto/x509" "encoding/json" "fmt" "github.com/notaryproject/notation-core-go/signature" "github.com/notaryproject/notation-go/config" "github.com/notaryproject/notation-go/dir" "github.com/notaryproject/notation-go/plugin/proto" "github.com/notaryproject/notation/test/e2e/plugin/internal/io" "github.com/spf13/cobra" ) func describeKeyCommand() *cobra.Command { return &cobra.Command{ Use: "describe-key", RunE: func(cmd *cobra.Command, args []string) error { req := &proto.DescribeKeyRequest{} if err := io.UnmarshalRequest(req); err != nil { return &proto.RequestError{Code: proto.ErrorCodeValidation, Err: err} } if err := validateDescribeKeyRequest(*req); err != nil { return &proto.RequestError{Code: proto.ErrorCodeValidation, Err: err} } return runDescribeKey(req) }, } } func runDescribeKey(req *proto.DescribeKeyRequest) error { // load cert chain by keyID _, certs, err := loadCertChain(req.KeyID) if err != nil { return &proto.RequestError{Code: ErrorCodeInvalidCertificate, Err: err} } // prepare response ks, err := signature.ExtractKeySpec(certs[0]) if err != nil { return &proto.RequestError{Code: ErrorCodeInvalidCertificate, Err: err} } keySpec, err := proto.EncodeKeySpec(ks) if err != nil { return &proto.RequestError{Code: proto.ErrorCodeGeneric, Err: err} } return io.PrintResponse(&proto.DescribeKeyResponse{ KeyID: req.KeyID, KeySpec: keySpec, }) } func loadCertChain(keyID string) (crypto.PrivateKey, []*x509.Certificate, error) { keyPath, certPath, err := getKeyPairPath(keyID) if err != nil { return nil, nil, err } certs, err := tls.LoadX509KeyPair(certPath, keyPath) if err != nil { return nil, nil, err } if len(certs.Certificate) == 0 { return nil, nil, fmt.Errorf("%q does not contain certificate", certPath) } var certChain []*x509.Certificate for _, cert := range certs.Certificate { c, err := x509.ParseCertificate(cert) if err != nil { return nil, nil, err } certChain = append(certChain, c) } return certs.PrivateKey, certChain, nil } func getKeyPairPath(keyId string) (string, string, error) { cfg := &config.SigningKeys{} file, err := dir.ConfigFS().Open("pluginkeys.json") if err != nil { return "", "", err } defer file.Close() if err := json.NewDecoder(file).Decode(cfg); err != nil { return "", "", err } var keyPath, certPath string for _, key := range cfg.Keys { if key.ID == keyId { keyPath = key.KeyPath certPath = key.CertificatePath break } } if keyPath == "" || certPath == "" { return "", "", fmt.Errorf("keyPath or certPath did't find for keyId: %s", keyId) } return keyPath, certPath, nil } func validateDescribeKeyRequest(req proto.DescribeKeyRequest) error { return validateRequiredField(req, fieldSet("ContractVesion", "KeyId")) } notation-1.2.0/test/e2e/plugin/errors.go000066400000000000000000000016471466375446100201700ustar00rootroot00000000000000// Copyright The Notary Project Authors. // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package main import "github.com/notaryproject/notation-go/plugin/proto" const ( ErrorCodeInvalidKeyID proto.ErrorCode = "INVALID_KEY_ID" ErrorCodeInvalidCertificate proto.ErrorCode = "INVALID_CERTIFICATE" ErrorCodeConfigError proto.ErrorCode = "CONFIG_ERROR" ErrorCodeSigningError proto.ErrorCode = "SIGNING_ERROR" ) notation-1.2.0/test/e2e/plugin/generate_envelope.go000066400000000000000000000130121466375446100223300ustar00rootroot00000000000000// Copyright The Notary Project Authors. // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package main import ( "crypto" "crypto/x509" "errors" "strings" "time" "github.com/notaryproject/notation-core-go/signature" _ "github.com/notaryproject/notation-core-go/signature/cose" _ "github.com/notaryproject/notation-core-go/signature/jws" "github.com/notaryproject/notation-go/plugin/proto" "github.com/notaryproject/notation-go/verifier" "github.com/notaryproject/notation/test/e2e/plugin/internal/io" "github.com/notaryproject/notation/test/e2e/plugin/mock" "github.com/spf13/cobra" ) const MediaTypePayloadV1 = "application/vnd.cncf.notary.payload.v1+json" func generateEnvelopeCommand() *cobra.Command { return &cobra.Command{ Use: "generate-envelope", RunE: func(cmd *cobra.Command, args []string) error { req := &proto.GenerateEnvelopeRequest{} if err := io.UnmarshalRequest(req); err != nil { return &proto.RequestError{Code: proto.ErrorCodeValidation, Err: err} } // validate request if err := validateGenerateEnvelopeRequest(*req); err != nil { return &proto.RequestError{Code: proto.ErrorCodeValidation, Err: err} } return runGenerateEnvelopeCommand(req) }, } } func runGenerateEnvelopeCommand(req *proto.GenerateEnvelopeRequest) error { // load cert chain by keyID privateKey, certs, err := loadCertChain(req.KeyID) if err != nil { return &proto.RequestError{Code: ErrorCodeInvalidCertificate, Err: err} } // prepare to sign signer, err := newSigner(privateKey, certs) if err != nil { return &proto.RequestError{Code: ErrorCodeSigningError, Err: err} } opts := extractSigningOpts(req) sig, err := signer.sign(req.Payload, req.SignatureEnvelopeType, opts) if err != nil { return &proto.RequestError{Code: ErrorCodeSigningError, Err: err} } // prepare response resp := &proto.GenerateEnvelopeResponse{ SignatureEnvelope: sig, SignatureEnvelopeType: req.SignatureEnvelopeType, Annotations: map[string]string{"signer": "e2e-plugin"}, } // update response for testing various cases if err := updateGenerateEnvelopeResponse(req, resp); err != nil { return err } return io.PrintResponse(resp) } // validateGenerateEnvelopeRequest validates required failed existence. func validateGenerateEnvelopeRequest(req proto.GenerateEnvelopeRequest) error { return validateRequiredField(req, fieldSet( "ContractVersion", "KeyID", "PayloadType", "SignatureEnvelopeType", "Payload", )) } // signer uses notation-core-go to sign the payload. type signer struct { signature.Signer } func newSigner(key crypto.PrivateKey, certChain []*x509.Certificate) (*signer, error) { localSigner, err := signature.NewLocalSigner(certChain, key) if err != nil { return nil, err } return &signer{Signer: localSigner}, nil } func (s *signer) sign(payload []byte, signatureMediaType string, opts *signingOpts) ([]byte, error) { signReq := &signature.SignRequest{ Payload: signature.Payload{ ContentType: MediaTypePayloadV1, Content: payload, }, Signer: s.Signer, SigningTime: time.Now(), SigningScheme: signature.SigningSchemeX509, SigningAgent: "e2e-plugin", ExtendedSignedAttributes: opts.extendedAttribute, } // Add expiry only if ExpiryDuration is not zero if opts.expiryDuration != 0 { signReq.Expiry = signReq.SigningTime.Add(opts.expiryDuration) } // perform signing sigEnv, err := signature.NewEnvelope(signatureMediaType) if err != nil { return nil, err } return sigEnv.Sign(signReq) } type signingOpts struct { extendedAttribute []signature.Attribute expiryDuration time.Duration } func extractSigningOpts(req *proto.GenerateEnvelopeRequest) *signingOpts { opts := &signingOpts{} // update expiry duration if req.ExpiryDurationInSeconds != 0 { opts.expiryDuration = time.Duration(req.ExpiryDurationInSeconds) * time.Second } // update extended Attributes if v, ok := req.PluginConfig[verifier.HeaderVerificationPlugin]; ok { opts.extendedAttribute = append(opts.extendedAttribute, signature.Attribute{ Key: verifier.HeaderVerificationPlugin, Critical: true, Value: v, }) } if v, ok := req.PluginConfig[verifier.HeaderVerificationPluginMinVersion]; ok { opts.extendedAttribute = append(opts.extendedAttribute, signature.Attribute{ Key: verifier.HeaderVerificationPluginMinVersion, Critical: true, Value: v, }) } return opts } // updateGenerateEnvelopeResponse tampers the response to mock various cases. func updateGenerateEnvelopeResponse(req *proto.GenerateEnvelopeRequest, resp *proto.GenerateEnvelopeResponse) error { if v, ok := req.PluginConfig[mock.TamperSignatureEnvelope]; ok { resp.SignatureEnvelope = []byte(v) } if v, ok := req.PluginConfig[mock.TamperSignatureEnvelopeType]; ok { resp.SignatureEnvelopeType = v } if v, ok := req.PluginConfig[mock.TamperAnnotation]; ok { kv := strings.Split(v, "=") if len(kv) != 2 { return errors.New("invalid annotation") } resp.Annotations = map[string]string{ kv[0]: kv[1], } } return nil } notation-1.2.0/test/e2e/plugin/generate_signature.go000066400000000000000000000121011466375446100225120ustar00rootroot00000000000000// Copyright The Notary Project Authors. // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package main import ( "crypto" "crypto/x509" "encoding/base64" "fmt" "github.com/golang-jwt/jwt" "github.com/notaryproject/notation-core-go/signature" "github.com/notaryproject/notation-go/plugin/proto" "github.com/notaryproject/notation-plugin-framework-go/plugin" "github.com/notaryproject/notation/test/e2e/plugin/internal/io" "github.com/notaryproject/notation/test/e2e/plugin/mock" "github.com/spf13/cobra" ) var ( ps256 = jwt.SigningMethodPS256.Name ps384 = jwt.SigningMethodPS384.Name ps512 = jwt.SigningMethodPS512.Name es256 = jwt.SigningMethodES256.Name es384 = jwt.SigningMethodES384.Name es512 = jwt.SigningMethodES512.Name ) var validMethods = []string{ps256, ps384, ps512, es256, es384, es512} var signatureAlgJWSAlgMap = map[signature.Algorithm]string{ signature.AlgorithmPS256: ps256, signature.AlgorithmPS384: ps384, signature.AlgorithmPS512: ps512, signature.AlgorithmES256: es256, signature.AlgorithmES384: es384, signature.AlgorithmES512: es512, } func generateSignatureCommand() *cobra.Command { return &cobra.Command{ Use: "generate-signature", RunE: func(cmd *cobra.Command, args []string) error { req := &proto.GenerateSignatureRequest{} if err := io.UnmarshalRequest(req); err != nil { return &proto.RequestError{Code: proto.ErrorCodeValidation, Err: err} } // validate request if err := validateGenerateSignatureRequest(*req); err != nil { return &proto.RequestError{Code: proto.ErrorCodeValidation, Err: err} } return runGenerateSignature(req) }, } } func runGenerateSignature(req *proto.GenerateSignatureRequest) error { // load cert chain by keyID privateKey, certs, err := loadCertChain(req.KeyID) if err != nil { return &proto.RequestError{Code: ErrorCodeInvalidCertificate, Err: err} } // prepare to sign algorithm, err := extractAlgorithm(req) if err != nil { return &proto.RequestError{Code: proto.ErrorCodeValidation, Err: err} } rawSig, err := sign(string(req.Payload), privateKey, algorithm) if err != nil { return &proto.RequestError{Code: proto.ErrorCodeGeneric, Err: err} } // prepare response signingAlg, err := proto.EncodeSigningAlgorithm(algorithm) if err != nil { return &proto.RequestError{Code: proto.ErrorCodeGeneric, Err: err} } resp := &plugin.GenerateSignatureResponse{ KeyID: req.KeyID, Signature: rawSig, SigningAlgorithm: signingAlg, CertificateChain: toRawCerts(certs), } // update response for testing various cases updateGenerateSignatureResponse(req, resp) return io.PrintResponse(resp) } func sign(payload string, privateKey crypto.PrivateKey, algorithm signature.Algorithm) ([]byte, error) { jwtAlg, err := toJWTAlgorithm(algorithm) if err != nil { return nil, err } // use JWT package to sign raw signature. method := jwt.GetSigningMethod(jwtAlg) sig, err := method.Sign(payload, privateKey) if err != nil { return nil, err } return base64.RawURLEncoding.DecodeString(sig) } func toRawCerts(certs []*x509.Certificate) [][]byte { var rawCerts [][]byte for _, cert := range certs { rawCerts = append(rawCerts, cert.Raw) } return rawCerts } func extractAlgorithm(req *proto.GenerateSignatureRequest) (signature.Algorithm, error) { // extract algorithm from signer keySpec, err := proto.DecodeKeySpec(req.KeySpec) if err != nil { return -1, err } return keySpec.SignatureAlgorithm(), nil } func toJWTAlgorithm(alg signature.Algorithm) (string, error) { // converts the signature.Algorithm to be jwt package defined // algorithm name. jwsAlg, ok := signatureAlgJWSAlgMap[alg] if !ok { return "", &signature.UnsupportedSignatureAlgoError{ Alg: fmt.Sprintf("#%d", alg)} } return jwsAlg, nil } // validateGenerateSignatureRequest validates required field existence. func validateGenerateSignatureRequest(req proto.GenerateSignatureRequest) error { return validateRequiredField(req, fieldSet( "ContractVersion", "KeyID", "KeySpec", "Hash", "Payload")) } // updateGenerateSignatureResponse tampers the response to test various cases. func updateGenerateSignatureResponse(req *plugin.GenerateSignatureRequest, resp *plugin.GenerateSignatureResponse) { if v, ok := req.PluginConfig[mock.TamperKeyID]; ok { resp.KeyID = v } if v, ok := req.PluginConfig[mock.TamperSignature]; ok { resp.Signature = []byte(v) } if v, ok := req.PluginConfig[mock.TamperSignatureAlgorithm]; ok { resp.SigningAlgorithm = plugin.SignatureAlgorithm(v) } if v, ok := req.PluginConfig[mock.TamperCertificateChain]; ok { resp.CertificateChain = [][]byte{[]byte(v)} } } notation-1.2.0/test/e2e/plugin/get_metadata.go000066400000000000000000000041331466375446100212640ustar00rootroot00000000000000// Copyright The Notary Project Authors. // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package main import ( "github.com/notaryproject/notation-go/plugin/proto" "github.com/notaryproject/notation/test/e2e/plugin/internal/io" "github.com/spf13/cobra" ) func getPluginMetadataCommand() *cobra.Command { return &cobra.Command{ Use: "get-plugin-metadata", RunE: func(cmd *cobra.Command, args []string) error { req := &proto.GetMetadataRequest{} if err := io.UnmarshalRequest(req); err != nil { return err } return runGetPluginMetadata(req) }, } } func runGetPluginMetadata(req *proto.GetMetadataRequest) error { resp := &proto.GetMetadataResponse{ Name: "e2e-plugin", Description: "The e2e-plugin is a Notation compatible plugin for Notation E2E test", Version: "1.0.0", URL: "https://github.com/notaryproject/notation/test/e2e/plugin", SupportedContractVersions: []string{"1.0"}, Capabilities: []proto.Capability{ proto.CapabilityTrustedIdentityVerifier, proto.CapabilityRevocationCheckVerifier, }, } // enable signing capability by PluginConfig checkCapability(req.PluginConfig, proto.CapabilitySignatureGenerator, resp) checkCapability(req.PluginConfig, proto.CapabilityEnvelopeGenerator, resp) // output the response return io.PrintResponse(resp) } func checkCapability(pluginConfig map[string]string, capability proto.Capability, resp *proto.GetMetadataResponse) { if v, ok := pluginConfig[string(capability)]; ok && v == "true" { resp.Capabilities = append(resp.Capabilities, capability) } } notation-1.2.0/test/e2e/plugin/go.mod000066400000000000000000000022141466375446100174220ustar00rootroot00000000000000module github.com/notaryproject/notation/test/e2e/plugin go 1.23 require ( github.com/golang-jwt/jwt v3.2.2+incompatible github.com/notaryproject/notation-core-go v1.1.0 github.com/notaryproject/notation-go v1.2.0 github.com/notaryproject/notation-plugin-framework-go v1.0.0 github.com/spf13/cobra v1.7.0 ) require ( github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 // indirect github.com/fxamacker/cbor/v2 v2.7.0 // indirect github.com/go-asn1-ber/asn1-ber v1.5.5 // indirect github.com/go-ldap/ldap/v3 v3.4.8 // indirect github.com/golang-jwt/jwt/v4 v4.5.0 // indirect github.com/google/uuid v1.6.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/notaryproject/tspclient-go v0.2.0 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.1.0 // indirect github.com/spf13/pflag v1.0.5 // indirect github.com/veraison/go-cose v1.1.0 // indirect github.com/x448/float16 v0.8.4 // indirect golang.org/x/crypto v0.26.0 // indirect golang.org/x/mod v0.20.0 // indirect golang.org/x/sync v0.6.0 // indirect oras.land/oras-go/v2 v2.5.0 // indirect ) notation-1.2.0/test/e2e/plugin/go.sum000066400000000000000000000270261466375446100174570ustar00rootroot00000000000000github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 h1:mFRzDkZVAjdal+s7s0MwaRv9igoPqLRdzOLzw/8Xvq8= github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU= github.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa h1:LHTHcTQiSGT7VVbI0o4wBRNQIgn917usHWOd6VAffYI= github.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa/go.mod h1:cEWa1LVoE5KvSD9ONXsZrj0z6KqySlCCNKHlLzbqAt4= github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E= github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= github.com/go-asn1-ber/asn1-ber v1.5.5 h1:MNHlNMBDgEKD4TcKr36vQN68BA00aDfjIt3/bD50WnA= github.com/go-asn1-ber/asn1-ber v1.5.5/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0= github.com/go-ldap/ldap/v3 v3.4.8 h1:loKJyspcRezt2Q3ZRMq2p/0v8iOurlmeXDPw6fikSvQ= github.com/go-ldap/ldap/v3 v3.4.8/go.mod h1:qS3Sjlu76eHfHGpUdWkAXQTw4beih+cHsco2jXlIXrk= github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY= github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg= github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4= github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM= github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8= github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/jcmturner/aescts/v2 v2.0.0 h1:9YKLH6ey7H4eDBXW8khjYslgyqG2xZikXP0EQFKrle8= github.com/jcmturner/aescts/v2 v2.0.0/go.mod h1:AiaICIRyfYg35RUkr8yESTqvSy7csK90qZ5xfvvsoNs= github.com/jcmturner/dnsutils/v2 v2.0.0 h1:lltnkeZGL0wILNvrNiVCR6Ro5PGU/SeBvVO/8c/iPbo= github.com/jcmturner/dnsutils/v2 v2.0.0/go.mod h1:b0TnjGOvI/n42bZa+hmXL+kFJZsFT7G4t3HTlQ184QM= github.com/jcmturner/gofork v1.7.6 h1:QH0l3hzAU1tfT3rZCnW5zXl+orbkNMMRGJfdJjHVETg= github.com/jcmturner/gofork v1.7.6/go.mod h1:1622LH6i/EZqLloHfE7IeZ0uEJwMSUyQ/nDd82IeqRo= github.com/jcmturner/goidentity/v6 v6.0.1 h1:VKnZd2oEIMorCTsFBnJWbExfNN7yZr3EhJAxwOkZg6o= github.com/jcmturner/goidentity/v6 v6.0.1/go.mod h1:X1YW3bgtvwAXju7V3LCIMpY0Gbxyjn/mY9zx4tFonSg= github.com/jcmturner/gokrb5/v8 v8.4.4 h1:x1Sv4HaTpepFkXbt2IkL29DXRf8sOfZXo8eRKh687T8= github.com/jcmturner/gokrb5/v8 v8.4.4/go.mod h1:1btQEpgT6k+unzCwX1KdWMEwPPkkgBtP+F6aCACiMrs= github.com/jcmturner/rpc/v2 v2.0.3 h1:7FXXj8Ti1IaVFpSAziCZWNzbNuZmnvw/i6CqLNdWfZY= github.com/jcmturner/rpc/v2 v2.0.3/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJki3ELg/Hc= github.com/notaryproject/notation-core-go v1.1.0 h1:xCybcONOKcCyPNihJUSa+jRNsyQFNkrk0eJVVs1kWeg= github.com/notaryproject/notation-core-go v1.1.0/go.mod h1:+6AOh41JPrnVLbW/19SJqdhVHwKgIINBO/np0e7nXJA= github.com/notaryproject/notation-go v1.2.0 h1:Muq/S+Vyyerq/hefD1SUaIqFbNrhV/zgXi/M9sL4bpg= github.com/notaryproject/notation-go v1.2.0/go.mod h1:re9V+TfuNRaUq5e3NuNcCJN53++sL2KbnJrjGyOUpgE= github.com/notaryproject/notation-plugin-framework-go v1.0.0 h1:6Qzr7DGXoCgXEQN+1gTZWuJAZvxh3p8Lryjn5FaLzi4= github.com/notaryproject/notation-plugin-framework-go v1.0.0/go.mod h1:RqWSrTOtEASCrGOEffq0n8pSg2KOgKYiWqFWczRSics= github.com/notaryproject/tspclient-go v0.2.0 h1:g/KpQGmyk/h7j60irIRG1mfWnibNOzJ8WhLqAzuiQAQ= github.com/notaryproject/tspclient-go v0.2.0/go.mod h1:LGyA/6Kwd2FlM0uk8Vc5il3j0CddbWSHBj/4kxQDbjs= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug= github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I= github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/veraison/go-cose v1.1.0 h1:AalPS4VGiKavpAzIlBjrn7bhqXiXi4jbMYY/2+UC+4o= github.com/veraison/go-cose v1.1.0/go.mod h1:7ziE85vSq4ScFTg6wyoMXjucIGOf4JkFEZi/an96Ct4= github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw= golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0= golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= golang.org/x/net v0.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc= golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= oras.land/oras-go/v2 v2.5.0 h1:o8Me9kLY74Vp5uw07QXPiitjsw7qNXi8Twd+19Zf02c= oras.land/oras-go/v2 v2.5.0/go.mod h1:z4eisnLP530vwIOUOJeBIj0aGI0L1C3d53atvCBqZHg= notation-1.2.0/test/e2e/plugin/internal/000077500000000000000000000000001466375446100201315ustar00rootroot00000000000000notation-1.2.0/test/e2e/plugin/internal/io/000077500000000000000000000000001466375446100205405ustar00rootroot00000000000000notation-1.2.0/test/e2e/plugin/internal/io/io.go000066400000000000000000000016761466375446100215100ustar00rootroot00000000000000// Copyright The Notary Project Authors. // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package io import ( "encoding/json" "os" ) // UnmarshalRequest read the STDIN and unmarshal to struct pointed by req. func UnmarshalRequest(req any) error { return json.NewDecoder(os.Stdin).Decode(req) } // PrintResponse marshal and print the struct data pointed by resp. func PrintResponse(resp any) error { return json.NewEncoder(os.Stdout).Encode(resp) } notation-1.2.0/test/e2e/plugin/main.go000066400000000000000000000031451466375446100175730ustar00rootroot00000000000000// Copyright The Notary Project Authors. // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package main import ( "encoding/json" "errors" "os" "github.com/notaryproject/notation-go/plugin/proto" "github.com/spf13/cobra" ) const NOTATION_USERNAME = "NOTATION_USERNAME" const NOTATION_PASSWORD = "NOTATION_PASSWORD" func main() { cmd := &cobra.Command{ Use: "plugin for Notation E2E test", SilenceUsage: true, SilenceErrors: true, PersistentPreRunE: func(cmd *cobra.Command, args []string) error { // check registry credentials are eliminated if os.Getenv(NOTATION_USERNAME) != "" || os.Getenv(NOTATION_PASSWORD) != "" { return &proto.RequestError{ Code: proto.ErrorCodeValidation, Err: errors.New("registry credentials are not eliminated"), } } return nil }, } cmd.AddCommand( getPluginMetadataCommand(), describeKeyCommand(), generateSignatureCommand(), generateEnvelopeCommand(), verifySignatureCommand(), ) if err := cmd.Execute(); err != nil { if newErr := json.NewEncoder(os.Stderr).Encode(err); newErr != nil { panic(newErr) } os.Exit(1) } } notation-1.2.0/test/e2e/plugin/mock/000077500000000000000000000000001466375446100172465ustar00rootroot00000000000000notation-1.2.0/test/e2e/plugin/mock/common.go000066400000000000000000000017561466375446100210760ustar00rootroot00000000000000// Copyright The Notary Project Authors. // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package mock const ( TamperKeyID = "TAMPER_KEY_ID" TamperSignature = "TAMPER_SIGNATURE" TamperSignatureAlgorithm = "TAMPER_SIGNATURE_ALGORITHM" TamperCertificateChain = "TAMPER_CERTIFICATE_CHAIN" TamperSignatureEnvelope = "TAMPER_SIGNATURE_ENVELOPE" TamperSignatureEnvelopeType = "TAMPER_SIGNATURE_ENVELOPE_TYPE" TamperAnnotation = "TAMPER_ANNOTATION" ) notation-1.2.0/test/e2e/plugin/validator.go000066400000000000000000000022651466375446100206360ustar00rootroot00000000000000// Copyright The Notary Project Authors. // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package main import ( "fmt" "reflect" ) func fieldSet(name ...string) map[string]struct{} { fs := make(map[string]struct{}) for _, n := range name { fs[n] = struct{}{} } return fs } func validateRequiredField(data any, fieldSet map[string]struct{}) error { valueOfData := reflect.ValueOf(data) typeOfData := valueOfData.Type() for i := 0; i < valueOfData.NumField(); i++ { fieldName := typeOfData.Field(i).Name if _, ok := fieldSet[fieldName]; ok { // check the required field if valueOfData.Field(i).IsZero() { return fmt.Errorf("%s is not set", fieldName) } } } return nil } notation-1.2.0/test/e2e/plugin/verify_signature.go000066400000000000000000000062071466375446100222360ustar00rootroot00000000000000// Copyright The Notary Project Authors. // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package main import ( "github.com/notaryproject/notation-go/plugin/proto" "github.com/notaryproject/notation/test/e2e/plugin/internal/io" "github.com/spf13/cobra" ) func verifySignatureCommand() *cobra.Command { return &cobra.Command{ Use: "verify-signature", RunE: func(cmd *cobra.Command, args []string) error { req := &proto.VerifySignatureRequest{} if err := io.UnmarshalRequest(req); err != nil { return &proto.RequestError{Code: proto.ErrorCodeValidation, Err: err} } if err := validateVerifySignatureRequest(*req); err != nil { return &proto.RequestError{Code: proto.ErrorCodeValidation, Err: err} } return runVerifySignature(req) }, } } func runVerifySignature(req *proto.VerifySignatureRequest) error { return io.PrintResponse(extractVerificationResult(req)) } // validateVerifySignatureRequest validates required field existence. func validateVerifySignatureRequest(req proto.VerifySignatureRequest) error { // check req.Signature.CriticalAttributes if err := validateRequiredField(req.Signature.CriticalAttributes, fieldSet( "ContentType", "SigningScheme", "Expiry")); err != nil { return err } // check req.Signature if err := validateRequiredField(req.Signature, fieldSet("CertificateChain")); err != nil { return err } // check req.TrustPolicy if err := validateRequiredField(req.TrustPolicy, fieldSet( "TrustIdentities", "SignatureVerification")); err != nil { return err } return validateRequiredField(req, fieldSet("ContractVersion")) } func extractVerificationResult(req *proto.VerifySignatureRequest) *proto.VerifySignatureResponse { resp := &proto.VerifySignatureResponse{ VerificationResults: make(map[proto.Capability]*proto.VerificationResult), } // set verification result based on req.PluginConfig if v, ok := req.PluginConfig[string(proto.CapabilityRevocationCheckVerifier)]; !ok || v == "success" { resp.VerificationResults[proto.CapabilityRevocationCheckVerifier] = &proto.VerificationResult{ Success: true, } } else { resp.VerificationResults[proto.CapabilityRevocationCheckVerifier] = &proto.VerificationResult{ Success: false, Reason: "revocation check failed", } } if v, ok := req.PluginConfig[string(proto.CapabilityTrustedIdentityVerifier)]; !ok || v == "success" { resp.VerificationResults[proto.CapabilityTrustedIdentityVerifier] = &proto.VerificationResult{ Success: true, } } else { resp.VerificationResults[proto.CapabilityTrustedIdentityVerifier] = &proto.VerificationResult{ Success: false, Reason: "trusted identity check failed", } } return resp } notation-1.2.0/test/e2e/run.sh000077500000000000000000000072611466375446100161700ustar00rootroot00000000000000#!/bin/bash -e # Copyright The Notary Project Authors. # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. CWD=$(pwd) SUPPORTED_REGISTRY=("zot" "dockerhub") function help { echo "Usage" echo " run.sh [old-notation-binary-path]" echo "" echo "Arguments" echo " registry-name is the registry to use for running the E2E test. Currently support: ${SUPPORTED_REGISTRY[@]}." echo " notation-binary-path is the path of the notation executable binary file." echo " old-notation-binary-path is the path of an old notation executable bianry file. If it is not set, an RC.1 Notation will be downloaded automatically." } # check registry name REGISTRY_NAME=$1 if [ -z "$REGISTRY_NAME" ]; then echo "registry name is missing." help exit 1 fi # check notation binary path. export NOTATION_E2E_BINARY_PATH=$(if [ ! -z "$2" ]; then realpath $2; fi) if [ ! -f "$NOTATION_E2E_BINARY_PATH" ]; then echo "notation binary path doesn't exist." help exit 1 fi # check old notation binary path for forward compatibility test. export NOTATION_E2E_OLD_BINARY_PATH=$(if [ ! -z "$3" ]; then realpath $3; fi) if [ ! -f "$NOTATION_E2E_OLD_BINARY_PATH" ]; then OLD_NOTATION_DIR=/tmp/notation_old export NOTATION_E2E_OLD_BINARY_PATH=$OLD_NOTATION_DIR/notation mkdir -p $OLD_NOTATION_DIR echo "Old notation binary path doesn't exist." echo "Try to use old notation binary at $NOTATION_E2E_OLD_BINARY_PATH" if [ ! -f $NOTATION_E2E_OLD_BINARY_PATH ]; then TAG=1.0.0-rc.5 # without 'v' echo "Didn't find old notation binary locally. Try to download notation v$TAG." TAR_NAME=notation_${TAG}_linux_amd64.tar.gz URL=https://github.com/notaryproject/notation/releases/download/v${TAG}/$TAR_NAME wget $URL -P $OLD_NOTATION_DIR tar -xf $OLD_NOTATION_DIR/$TAR_NAME -C $OLD_NOTATION_DIR if [ ! -f $NOTATION_E2E_OLD_BINARY_PATH ]; then echo "Failed to download old notation binary for forward compatibility test." exit 1 fi echo "Downloaded notation v$TAG at $NOTATION_E2E_OLD_BINARY_PATH" fi fi # install dependency go install -mod=mod github.com/onsi/ginkgo/v2/ginkgo@v2.11.0 # build e2e plugin and tar.gz PLUGIN_NAME=notation-e2e-plugin ( cd $CWD/plugin && go build -o ./bin/$PLUGIN_NAME . && echo "e2e plugin built." && tar -czvf ./bin/$PLUGIN_NAME.tar.gz -C ./bin/ $PLUGIN_NAME ) # setup registry case $REGISTRY_NAME in "zot") source ./scripts/zot.sh ;; "dockerhub") source ./scripts/dockerhub.sh ;; *) echo "invalid registry" help exit 1 ;; esac setup_registry # defer cleanup registry function cleanup { cleanup_registry } trap cleanup EXIT # set environment variable for E2E testing export NOTATION_E2E_CONFIG_PATH=$CWD/testdata/config export NOTATION_E2E_OCI_LAYOUT_PATH=$CWD/testdata/registry/oci_layout export NOTATION_E2E_TEST_REPO=e2e export NOTATION_E2E_TEST_TAG=v1 export NOTATION_E2E_PLUGIN_PATH=$CWD/plugin/bin/$PLUGIN_NAME export NOTATION_E2E_PLUGIN_TAR_GZ_PATH=$CWD/plugin/bin/$PLUGIN_NAME.tar.gz export NOTATION_E2E_MALICIOUS_PLUGIN_ARCHIVE_PATH=$CWD/testdata/malicious-plugin # run tests ginkgo -r -p -v notation-1.2.0/test/e2e/scripts/000077500000000000000000000000001466375446100165065ustar00rootroot00000000000000notation-1.2.0/test/e2e/scripts/dockerhub.sh000066400000000000000000000053261466375446100210160ustar00rootroot00000000000000#!/bin/bash -e # Copyright The Notary Project Authors. # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # this script called by ../run.sh # # DockerHub has image pulling limit and request rate limit. Please improve your # subscription level if you confronts those issues. # # Usage # export DOCKER_USERNAME=xxx # export DOCKER_PASSOWRD=xxx # ./run.sh dockerhub [old-notation-binary-path] # check required environment variable if [ -z "$DOCKER_USERNAME" ] || [ -z "$DOCKER_PASSWORD" ]; then echo "\$DOCKER_USERNAME or \$DOCKER_PASSWORD is not set" exit 1 fi # set environment variables for E2E testing export NOTATION_E2E_REGISTRY_HOST="docker.io/$DOCKER_USERNAME" export NOTATION_E2E_REGISTRY_USERNAME="$DOCKER_USERNAME" export NOTATION_E2E_REGISTRY_PASSWORD="$DOCKER_PASSWORD" export NOTATION_E2E_DOMAIN_REGISTRY_HOST="$NOTATION_E2E_REGISTRY_HOST" function setup_registry { echo "use $NOTATION_E2E_REGISTRY_HOST" } function cleanup_registry { echo "cleaning dockerhub" # get token # reference: https://docs.docker.com/docker-hub/api/latest/#tag/authentication HUB_TOKEN=$(curl -s -H "Content-Type: application/json" -X POST -d "{\"username\": \"$DOCKER_USERNAME\", \"password\": \"$DOCKER_PASSWORD\"}" https://hub.docker.com/v2/users/login/ | jq -r .token) for (( page=1;;page++ )); do # page query the repositorys' name resp=`curl -s -X GET \ -H "Accept: application/json" \ -H "Authorization: JWT $HUB_TOKEN" \ "https://hub.docker.com/v2/repositories/$DOCKER_USERNAME/?page_size=100&&page=$page"` # check the last page if [[ "$resp" == *"object not found"* ]]; then break fi # parse json and extract e2e repoName e2eRepos=(`echo $resp | jq -r '.results|.[]|.name' | grep 'e2e-'`) echo "repositories: ${e2eRepos[@]}" for repoName in "${e2eRepos[@]}"; do # run delete curl -X DELETE \ -H "Accept: application/json" \ -H "Authorization: JWT $HUB_TOKEN" \ https://hub.docker.com/v2/repositories/$DOCKER_USERNAME/$repoName/ && \ echo "$NOTATION_E2E_REGISTRY_HOST/$repoName deleted." done done } notation-1.2.0/test/e2e/scripts/tls.sh000066400000000000000000000041421466375446100176450ustar00rootroot00000000000000#!/bin/bash -e # Copyright The Notary Project Authors. # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # Usage # For setup: # 1. source ./scripts/tls.sh # 2. call create_docker_network # 3. setup registry with port 5000 in $DOCKER_NETWORK # 4. call setup_tls reverse proxy # # For clean up: # 1. call clean_up # 2. clean up registry # 3. call remove_docker_network # # Note: this script needs sudo permission to add TLS certificate to system and # add domain registry host. NGINX_CONTAINER_NAME=nginx DOMAIN=notation-e2e.registry.io DOCKER_NETWORK=notation-e2e function create_docker_network { docker network create "$DOCKER_NETWORK" } function remove_docker_network { docker network rm "$DOCKER_NETWORK" } function setup_tls { # add domain registry host to /etc/hosts for testing --plain-http feature echo "127.0.0.1 $DOMAIN" | sudo tee -a /etc/hosts # add TLS certificate to system sudo mkdir -p /usr/local/share/ca-certificates/ sudo cp ./testdata/nginx/notation-e2e.registry.io.crt /usr/local/share/ca-certificates/ sudo update-ca-certificates # start Nginx for TLS docker run -d -p 80:80 -p 443:443 \ --network "$DOCKER_NETWORK" \ --mount type=bind,source="$(pwd)/testdata/nginx/",target=/etc/nginx \ --name "$NGINX_CONTAINER_NAME" \ --rm nginx:latest } function clean_up_tls { docker container stop "$NGINX_CONTAINER_NAME" 1>/dev/null && echo "Nginx stopped" sudo sed -i "/${NOTATION_E2E_DOMAIN_REGISTRY_HOST}/d" /etc/hosts sudo rm /usr/local/share/ca-certificates/notation-e2e.registry.io.crt sudo update-ca-certificates } notation-1.2.0/test/e2e/scripts/zot.sh000066400000000000000000000033031466375446100176550ustar00rootroot00000000000000#!/bin/bash -e # Copyright The Notary Project Authors. # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # this script called by ../run.sh # # Usage # ./run.sh zot [old-notation-binary-path] source ./scripts/tls.sh REG_HOST=localhost REG_PORT=5000 ZOT_CONTAINER_NAME=notation-e2e-registry # set required environment variables for E2E testing export NOTATION_E2E_REGISTRY_HOST="$REG_HOST:$REG_PORT" export NOTATION_E2E_REGISTRY_USERNAME=testuser export NOTATION_E2E_REGISTRY_PASSWORD=testpassword export NOTATION_E2E_DOMAIN_REGISTRY_HOST="$DOMAIN" function setup_registry { create_docker_network # start Zot docker run -d -p $REG_PORT:$REG_PORT -it \ --name $ZOT_CONTAINER_NAME \ --network $DOCKER_NETWORK \ --mount type=bind,source="$(pwd)/testdata/registry/zot/",target=/etc/zot \ --rm ghcr.io/project-zot/zot-minimal-linux-amd64:latest if [ "$GITHUB_ACTIONS" == "true" ]; then setup_tls fi # make sure that Zot is ready sleep 1 } function cleanup_registry { docker container stop $ZOT_CONTAINER_NAME 1>/dev/null && echo "Zot stopped" if [ "$GITHUB_ACTIONS" == "true" ]; then clean_up_tls fi remove_docker_network } notation-1.2.0/test/e2e/suite/000077500000000000000000000000001466375446100161505ustar00rootroot00000000000000notation-1.2.0/test/e2e/suite/command/000077500000000000000000000000001466375446100175665ustar00rootroot00000000000000notation-1.2.0/test/e2e/suite/command/cert.go000066400000000000000000000065631466375446100210640ustar00rootroot00000000000000// Copyright The Notary Project Authors. // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package command import ( . "github.com/notaryproject/notation/test/e2e/internal/notation" "github.com/notaryproject/notation/test/e2e/internal/utils" // . "github.com/notaryproject/notation/test/e2e/suite/common" . "github.com/onsi/ginkgo/v2" ) var _ = Describe("notation cert", func() { It("show all", func() { Host(BaseOptions(), func(notation *utils.ExecOpts, artifact *Artifact, vhost *utils.VirtualHost) { notation.Exec("cert", "list"). MatchKeyWords( "STORE TYPE STORE NAME CERTIFICATE", ) }) }) It("delete all", func() { Host(BaseOptions(), func(notation *utils.ExecOpts, artifact *Artifact, vhost *utils.VirtualHost) { notation.Exec("cert", "delete", "--all", "--type", "ca", "--store", "e2e", "-y"). MatchKeyWords( "Successfully deleted", ) }) }) It("delete a specfic cert", func() { Host(BaseOptions(), func(notation *utils.ExecOpts, artifact *Artifact, vhost *utils.VirtualHost) { notation.Exec("cert", "delete", "--type", "ca", "--store", "e2e", "e2e.crt", "-y"). MatchKeyWords( "Successfully deleted e2e.crt", ) }) }) It("delete a non-exist cert", func() { Host(BaseOptions(), func(notation *utils.ExecOpts, artifact *Artifact, vhost *utils.VirtualHost) { notation.ExpectFailure().Exec("cert", "delete", "--type", "ca", "--store", "e2e", "non-exist.crt", "-y"). MatchErrKeyWords( "failed to delete the certificate file", ) }) }) It("show e2e cert", func() { Host(BaseOptions(), func(notation *utils.ExecOpts, artifact *Artifact, vhost *utils.VirtualHost) { notation.Exec("cert", "show", "--type", "ca", "--store", "e2e", "e2e.crt"). MatchKeyWords( "Issuer: CN=e2e,O=Notary,L=Seattle,ST=WA,C=US", ) }) }) It("list", func() { Host(BaseOptions(), func(notation *utils.ExecOpts, artifact *Artifact, vhost *utils.VirtualHost) { notation.Exec("cert", "list"). MatchKeyWords( "STORE TYPE STORE NAME CERTIFICATE", ) }) }) It("list with type", func() { Host(BaseOptions(), func(notation *utils.ExecOpts, artifact *Artifact, vhost *utils.VirtualHost) { notation.Exec("cert", "list", "--type", "ca"). MatchKeyWords( "STORE TYPE STORE NAME CERTIFICATE", "e2e.crt", ) }) }) It("list with store", func() { Host(BaseOptions(), func(notation *utils.ExecOpts, artifact *Artifact, vhost *utils.VirtualHost) { notation.Exec("cert", "list", "--store", "e2e"). MatchKeyWords( "STORE TYPE STORE NAME CERTIFICATE", "e2e.crt", ) }) }) It("list with type and store", func() { Host(BaseOptions(), func(notation *utils.ExecOpts, artifact *Artifact, vhost *utils.VirtualHost) { notation.Exec("cert", "list", "--type", "ca", "--store", "e2e"). MatchKeyWords( "STORE TYPE STORE NAME CERTIFICATE", "e2e.crt", ) }) }) }) notation-1.2.0/test/e2e/suite/command/command_test.go000066400000000000000000000014211466375446100225700ustar00rootroot00000000000000// Copyright The Notary Project Authors. // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package command import ( "testing" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" ) func TestCommand(t *testing.T) { RegisterFailHandler(Fail) RunSpecs(t, "Command Suite") } notation-1.2.0/test/e2e/suite/command/inspect.go000066400000000000000000000144031466375446100215640ustar00rootroot00000000000000// Copyright The Notary Project Authors. // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package command import ( "path/filepath" . "github.com/notaryproject/notation/test/e2e/internal/notation" "github.com/notaryproject/notation/test/e2e/internal/utils" . "github.com/notaryproject/notation/test/e2e/suite/common" . "github.com/onsi/ginkgo/v2" ) var ( inspectSuccessfully = []string{ "└── application/vnd.cncf.notary.signature", "└── sha256:", "├── media type:", "├── signature algorithm:", "├── signed attributes", "signingTime:", "signingScheme:", "├── user defined attributes", "│ └── (empty)", "├── unsigned attributes", "│ └── signingAgent: notation-go/", "├── certificates", "│ └── SHA256 fingerprint:", "issued to:", "issued by:", "expiry:", "└── signed artifact", "media type:", "digest:", "size:", } inspectSuccessfullyWithTimestamp = []string{ "└── application/vnd.cncf.notary.signature", "└── sha256:", "├── media type:", "├── signature algorithm:", "├── signed attributes", "signingTime:", "signingScheme:", "├── user defined attributes", "│ └── (empty)", "├── unsigned attributes", "signingAgent: notation-go/", "timestamp signature", "timestamp:", "certificates", "SHA256 fingerprint:", "├── certificates", "│ └── SHA256 fingerprint:", "issued to:", "issued by:", "expiry:", "└── signed artifact", "media type:", "digest:", "size:", } ) var _ = Describe("notation inspect", func() { It("all signatures of an image", func() { Host(BaseOptions(), func(notation *utils.ExecOpts, artifact *Artifact, vhost *utils.VirtualHost) { notation.Exec("sign", artifact.ReferenceWithDigest()). MatchKeyWords(SignSuccessfully) notation.Exec("inspect", "-d", artifact.ReferenceWithDigest()). MatchKeyWords(inspectSuccessfully...) }) }) It("all signatures of an image with TLS", func() { HostInGithubAction(BaseOptions(), func(notation *utils.ExecOpts, artifact *Artifact, vhost *utils.VirtualHost) { notation.Exec("sign", artifact.DomainReferenceWithDigest()). MatchKeyWords(SignSuccessfully) notation.Exec("inspect", "-d", artifact.DomainReferenceWithDigest()). MatchKeyWords(inspectSuccessfully...). MatchErrKeyWords(HTTPSRequest). NoMatchErrKeyWords(HTTPRequest) }) }) It("all signatures of an image with --insecure-registry flag", func() { HostInGithubAction(BaseOptions(), func(notation *utils.ExecOpts, artifact *Artifact, vhost *utils.VirtualHost) { notation.Exec("sign", artifact.DomainReferenceWithDigest()). MatchKeyWords(SignSuccessfully) notation.Exec("inspect", "-d", "--insecure-registry", artifact.DomainReferenceWithDigest()). MatchKeyWords(inspectSuccessfully...). MatchErrKeyWords(HTTPRequest). NoMatchErrKeyWords(HTTPSRequest) }) }) It("sign with --force-referrers-tag set", func() { Host(BaseOptions(), func(notation *utils.ExecOpts, artifact *Artifact, vhost *utils.VirtualHost) { notation.Exec("sign", "--force-referrers-tag", artifact.ReferenceWithDigest()). MatchKeyWords(SignSuccessfully) notation.Exec("inspect", artifact.ReferenceWithDigest(), "-v"). MatchKeyWords(inspectSuccessfully...) }) }) It("sign with --force-referrers-tag set to false", func() { Host(BaseOptions(), func(notation *utils.ExecOpts, artifact *Artifact, vhost *utils.VirtualHost) { notation.Exec("sign", "--force-referrers-tag=false", artifact.ReferenceWithDigest()). MatchKeyWords(SignSuccessfully) notation.Exec("inspect", artifact.ReferenceWithDigest(), "-v"). MatchKeyWords(inspectSuccessfully...) }) }) It("sign with --allow-referrers-api set", func() { Host(BaseOptionsWithExperimental(), func(notation *utils.ExecOpts, artifact *Artifact, vhost *utils.VirtualHost) { notation.Exec("sign", "--allow-referrers-api", artifact.ReferenceWithDigest()). MatchKeyWords(SignSuccessfully) notation.Exec("inspect", artifact.ReferenceWithDigest(), "-v"). MatchKeyWords(inspectSuccessfully...) notation.Exec("inspect", artifact.ReferenceWithDigest(), "--allow-referrers-api", "-v"). MatchErrKeyWords( "Warning: This feature is experimental and may not be fully tested or completed and may be deprecated.", "Warning: flag '--allow-referrers-api' is deprecated and will be removed in future versions.", ). MatchKeyWords(inspectSuccessfully...) }) }) It("sign with --allow-referrers-api set to false", func() { Host(BaseOptionsWithExperimental(), func(notation *utils.ExecOpts, artifact *Artifact, vhost *utils.VirtualHost) { notation.Exec("sign", "--allow-referrers-api=false", artifact.ReferenceWithDigest()). MatchKeyWords(SignSuccessfully) notation.Exec("inspect", artifact.ReferenceWithDigest(), "-v"). MatchKeyWords(inspectSuccessfully...) notation.Exec("inspect", artifact.ReferenceWithDigest(), "--allow-referrers-api", "-v"). MatchErrKeyWords( "Warning: This feature is experimental and may not be fully tested or completed and may be deprecated.", "Warning: flag '--allow-referrers-api' is deprecated and will be removed in future versions.", ). MatchKeyWords(inspectSuccessfully...) }) }) It("with timestamping", func() { Host(BaseOptions(), func(notation *utils.ExecOpts, artifact *Artifact, vhost *utils.VirtualHost) { notation.Exec("sign", "--timestamp-url", "http://rfc3161timestamp.globalsign.com/advanced", "--timestamp-root-cert", filepath.Join(NotationE2EConfigPath, "timestamp", "globalsignTSARoot.cer"), artifact.ReferenceWithDigest()). MatchKeyWords(SignSuccessfully) notation.Exec("inspect", artifact.ReferenceWithDigest()). MatchKeyWords(inspectSuccessfullyWithTimestamp...) }) }) }) notation-1.2.0/test/e2e/suite/command/list.go000066400000000000000000000135641466375446100211010ustar00rootroot00000000000000// Copyright The Notary Project Authors. // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package command import ( . "github.com/notaryproject/notation/test/e2e/internal/notation" "github.com/notaryproject/notation/test/e2e/internal/utils" . "github.com/notaryproject/notation/test/e2e/suite/common" . "github.com/onsi/ginkgo/v2" ) var _ = Describe("notation list", func() { It("all signatures of an image", func() { Host(BaseOptions(), func(notation *utils.ExecOpts, artifact *Artifact, vhost *utils.VirtualHost) { notation.Exec("sign", artifact.ReferenceWithDigest()). MatchKeyWords(SignSuccessfully) notation.Exec("list", "-d", artifact.ReferenceWithDigest()). MatchKeyWords( "└── application/vnd.cncf.notary.signature", "└── sha256:", ) }) }) It("all signatures of an image with TLS", func() { HostInGithubAction(BaseOptions(), func(notation *utils.ExecOpts, artifact *Artifact, vhost *utils.VirtualHost) { notation.Exec("sign", artifact.DomainReferenceWithDigest()). MatchKeyWords(SignSuccessfully) notation.Exec("list", "-d", artifact.DomainReferenceWithDigest()). MatchKeyWords( "└── application/vnd.cncf.notary.signature", "└── sha256:", ). MatchErrKeyWords(HTTPSRequest). NoMatchErrKeyWords(HTTPRequest) }) }) It("all signatures of an image with --insecure-registry flag", func() { HostInGithubAction(BaseOptions(), func(notation *utils.ExecOpts, artifact *Artifact, vhost *utils.VirtualHost) { notation.Exec("sign", artifact.DomainReferenceWithDigest()). MatchKeyWords(SignSuccessfully) notation.Exec("list", "-d", "--insecure-registry", artifact.DomainReferenceWithDigest()). MatchKeyWords( "└── application/vnd.cncf.notary.signature", "└── sha256:", ). MatchErrKeyWords(HTTPRequest). NoMatchErrKeyWords(HTTPSRequest) }) }) It("all signatures of an oci-layout", func() { HostWithOCILayout(BaseOptionsWithExperimental(), func(notation *utils.ExecOpts, _ *OCILayout, vhost *utils.VirtualHost) { ociLayout, err := GenerateOCILayout("e2e-valid-signature") if err != nil { Fail(err.Error()) } notation.Exec("list", "--oci-layout", ociLayout.ReferenceWithDigest()). MatchKeyWords( "└── application/vnd.cncf.notary.signature", "└── sha256:90ceaff260d657d797c408ac73564a9c7bb9d86055877c2a811f0e63b8c6524f", ) }) }) It("oci-layout with no signature", func() { HostWithOCILayout(BaseOptionsWithExperimental(), func(notation *utils.ExecOpts, ociLayout *OCILayout, vhost *utils.VirtualHost) { notation.Exec("list", "--oci-layout", ociLayout.ReferenceWithDigest()). MatchKeyWords("has no associated signature") }) }) It("sign with --force-referrers-tag set", func() { Host(BaseOptions(), func(notation *utils.ExecOpts, artifact *Artifact, vhost *utils.VirtualHost) { notation.Exec("sign", "--force-referrers-tag", artifact.ReferenceWithDigest()). MatchKeyWords(SignSuccessfully) notation.Exec("list", artifact.ReferenceWithDigest(), "-v"). MatchKeyWords( "└── application/vnd.cncf.notary.signature", "└── sha256:", ) }) }) It("sign with --force-referrers-tag set to false", func() { Host(BaseOptions(), func(notation *utils.ExecOpts, artifact *Artifact, vhost *utils.VirtualHost) { notation.Exec("sign", "--force-referrers-tag=false", artifact.ReferenceWithDigest()). MatchKeyWords(SignSuccessfully) notation.Exec("list", artifact.ReferenceWithDigest(), "-v"). MatchKeyWords( "└── application/vnd.cncf.notary.signature", "└── sha256:", ) }) }) It("sign with --allow-referrers-api set", func() { Host(BaseOptionsWithExperimental(), func(notation *utils.ExecOpts, artifact *Artifact, vhost *utils.VirtualHost) { notation.Exec("sign", "--allow-referrers-api", artifact.ReferenceWithDigest()). MatchKeyWords(SignSuccessfully) notation.Exec("list", artifact.ReferenceWithDigest(), "-v"). MatchKeyWords( "└── application/vnd.cncf.notary.signature", "└── sha256:", ) notation.Exec("list", artifact.ReferenceWithDigest(), "--allow-referrers-api", "-v"). MatchErrKeyWords( "Warning: This feature is experimental and may not be fully tested or completed and may be deprecated.", "Warning: flag '--allow-referrers-api' is deprecated and will be removed in future versions.", ). MatchKeyWords( "└── application/vnd.cncf.notary.signature", "└── sha256:", ) }) }) It("sign with --allow-referrers-api set to false", func() { Host(BaseOptionsWithExperimental(), func(notation *utils.ExecOpts, artifact *Artifact, vhost *utils.VirtualHost) { notation.Exec("sign", "--allow-referrers-api=false", artifact.ReferenceWithDigest()). MatchKeyWords(SignSuccessfully) notation.Exec("list", artifact.ReferenceWithDigest(), "-v"). MatchKeyWords( "└── application/vnd.cncf.notary.signature", "└── sha256:", ) notation.Exec("list", artifact.ReferenceWithDigest(), "--allow-referrers-api", "-v"). MatchErrKeyWords( "Warning: This feature is experimental and may not be fully tested or completed and may be deprecated.", "Warning: flag '--allow-referrers-api' is deprecated and will be removed in future versions.", ). MatchKeyWords( "└── application/vnd.cncf.notary.signature", "└── sha256:", ) }) }) }) notation-1.2.0/test/e2e/suite/command/login.go000066400000000000000000000044241466375446100212310ustar00rootroot00000000000000// Copyright The Notary Project Authors. // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package command import ( "fmt" . "github.com/notaryproject/notation/test/e2e/internal/notation" "github.com/notaryproject/notation/test/e2e/internal/utils" . "github.com/notaryproject/notation/test/e2e/suite/common" . "github.com/onsi/ginkgo/v2" "github.com/onsi/gomega/gbytes" ) var _ = Describe("notation login", func() { BeforeEach(func() { Skip("The login tests require setting up credential helper running in host and it is not available in Github runner. Issue to remove this skip: https://github.com/notaryproject/notation/issues/587") }) It("should sign an image after successfully logging in the registry by prompt with a correct credential", func() { Host(TestLoginOptions(), func(notation *utils.ExecOpts, artifact *Artifact, vhost *utils.VirtualHost) { notation.WithInput(gbytes.BufferWithBytes([]byte(fmt.Sprintf("%s\n%s\n", TestRegistry.Username, TestRegistry.Password)))). Exec("login", artifact.Host). MatchKeyWords(LoginSuccessfully) notation.Exec("sign", artifact.ReferenceWithDigest()). MatchKeyWords(SignSuccessfully) notation.Exec("logout", artifact.Host). MatchKeyWords(LogoutSuccessfully) }) }) It("should fail to sign an image after failing to log in the registry with a wrong credential", func() { Host(TestLoginOptions(), func(notation *utils.ExecOpts, artifact *Artifact, vhost *utils.VirtualHost) { notation.WithInput(gbytes.BufferWithBytes([]byte(fmt.Sprintf("%s\n%s\n", "invalidUser", "invalidPassword")))). ExpectFailure(). Exec("login", artifact.Host). MatchErrKeyWords("unauthorized") notation.ExpectFailure(). Exec("sign", artifact.ReferenceWithDigest()). MatchErrKeyWords("credential required for basic auth") }) }) }) notation-1.2.0/test/e2e/suite/command/policy.go000066400000000000000000000224071466375446100214210ustar00rootroot00000000000000// Copyright The Notary Project Authors. // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package command import ( "os" "path/filepath" "strings" . "github.com/notaryproject/notation/test/e2e/internal/notation" "github.com/notaryproject/notation/test/e2e/internal/utils" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" ) var _ = Describe("trust policy maintainer", func() { When("showing configuration", func() { It("should show error and hint if policy doesn't exist", func() { Host(Opts(), func(notation *utils.ExecOpts, artifact *Artifact, vhost *utils.VirtualHost) { notation.ExpectFailure(). Exec("policy", "show"). MatchErrKeyWords("failed to show trust policy", "notation policy import") }) }) It("should show error and hint if policy without read permission", func() { Host(Opts(AddTrustPolicyOption(TrustPolicyName)), func(notation *utils.ExecOpts, artifact *Artifact, vhost *utils.VirtualHost) { trustPolicyPath := vhost.AbsolutePath(NotationDirName, TrustPolicyName) os.Chmod(trustPolicyPath, 0200) notation.ExpectFailure(). Exec("policy", "show"). MatchErrKeyWords("failed to show trust policy", "permission denied") }) }) It("should show exist policy", func() { content, err := os.ReadFile(filepath.Join(NotationE2ETrustPolicyDir, TrustPolicyName)) Expect(err).NotTo(HaveOccurred()) Host(Opts(AddTrustPolicyOption(TrustPolicyName)), func(notation *utils.ExecOpts, artifact *Artifact, vhost *utils.VirtualHost) { notation.Exec("policy", "show"). MatchContent(string(content)) }) }) It("should display error hint when showing invalid policy", func() { policyName := "invalid_format_trustpolicy.json" content, err := os.ReadFile(filepath.Join(NotationE2ETrustPolicyDir, policyName)) Expect(err).NotTo(HaveOccurred()) Host(Opts(AddTrustPolicyOption(policyName)), func(notation *utils.ExecOpts, artifact *Artifact, vhost *utils.VirtualHost) { notation.Exec("policy", "show"). MatchErrKeyWords("existing trust policy configuration is invalid"). MatchContent(string(content)) }) }) }) When("importing configuration without existing trust policy configuration", func() { opts := Opts() It("should fail if no file path is provided", func() { Host(opts, func(notation *utils.ExecOpts, artifact *Artifact, vhost *utils.VirtualHost) { notation.ExpectFailure(). Exec("policy", "import"). MatchErrKeyWords("requires 1 argument but received 0") }) }) It("should fail if more than one file path is provided", func() { Host(opts, func(notation *utils.ExecOpts, artifact *Artifact, vhost *utils.VirtualHost) { notation.ExpectFailure(). Exec("policy", "import", "a", "b"). MatchErrKeyWords("requires 1 argument but received 2") }) }) It("should fail if provided file doesn't exist", func() { Host(opts, func(notation *utils.ExecOpts, artifact *Artifact, vhost *utils.VirtualHost) { notation.ExpectFailure(). Exec("policy", "import", "/??/???") }) }) It("should fail if registry scope is malformed", func() { Host(opts, func(notation *utils.ExecOpts, artifact *Artifact, vhost *utils.VirtualHost) { notation.ExpectFailure(). Exec("policy", "import", filepath.Join(NotationE2ETrustPolicyDir, "malformed_registry_scope_trustpolicy.json")) }) }) It("should fail if store is malformed", func() { Host(opts, func(notation *utils.ExecOpts, artifact *Artifact, vhost *utils.VirtualHost) { notation.ExpectFailure(). Exec("policy", "import", filepath.Join(NotationE2ETrustPolicyDir, "malformed_trust_store_trustpolicy.json")) }) }) It("should fail if identity is malformed", func() { Host(opts, func(notation *utils.ExecOpts, artifact *Artifact, vhost *utils.VirtualHost) { notation.ExpectFailure(). Exec("policy", "import", filepath.Join(NotationE2ETrustPolicyDir, "malformed_trusted_identity_trustpolicy.json")) }) }) It("should import successfully", func() { Host(opts, func(notation *utils.ExecOpts, artifact *Artifact, vhost *utils.VirtualHost) { notation.Exec("policy", "import", filepath.Join(NotationE2ETrustPolicyDir, TrustPolicyName)) }) }) It("should import successfully by force", func() { Host(opts, func(notation *utils.ExecOpts, artifact *Artifact, vhost *utils.VirtualHost) { notation.Exec("policy", "import", filepath.Join(NotationE2ETrustPolicyDir, TrustPolicyName), "--force") }) }) }) When("importing configuration with existing trust policy configuration", func() { opts := Opts(AddTrustPolicyOption(TrustPolicyName)) It("should fail if no file path is provided", func() { Host(opts, func(notation *utils.ExecOpts, artifact *Artifact, vhost *utils.VirtualHost) { notation.ExpectFailure(). Exec("policy", "import") }) }) It("should fail if provided file doesn't exist", func() { Host(opts, func(notation *utils.ExecOpts, artifact *Artifact, vhost *utils.VirtualHost) { notation.ExpectFailure(). Exec("policy", "import", "/??/???", "--force") }) }) It("should fail if registry scope is malformed", func() { Host(opts, func(notation *utils.ExecOpts, artifact *Artifact, vhost *utils.VirtualHost) { notation.WithInput(strings.NewReader("Y\n")).ExpectFailure(). Exec("policy", "import", filepath.Join(NotationE2ETrustPolicyDir, "malformed_registry_scope_trustpolicy.json")) }) }) It("should fail if store is malformed", func() { Host(opts, func(notation *utils.ExecOpts, artifact *Artifact, vhost *utils.VirtualHost) { notation.WithInput(strings.NewReader("Y\n")).ExpectFailure(). Exec("policy", "import", filepath.Join(NotationE2ETrustPolicyDir, "malformed_trust_store_trustpolicy.json")) }) }) It("should fail if identity is malformed", func() { Host(opts, func(notation *utils.ExecOpts, artifact *Artifact, vhost *utils.VirtualHost) { notation.WithInput(strings.NewReader("Y\n")).ExpectFailure(). Exec("policy", "import", filepath.Join(NotationE2ETrustPolicyDir, "malformed_trusted_identity_trustpolicy.json")) }) }) It("should cancel import with N", func() { Host(opts, func(notation *utils.ExecOpts, artifact *Artifact, vhost *utils.VirtualHost) { notation.WithInput(strings.NewReader("N\n")).Exec("policy", "import", filepath.Join(NotationE2ETrustPolicyDir, "skip_trustpolicy.json")) // validate content, err := os.ReadFile(filepath.Join(NotationE2ETrustPolicyDir, TrustPolicyName)) Expect(err).NotTo(HaveOccurred()) notation.Exec("policy", "show").MatchContent(string(content)) }) }) It("should cancel import by default", func() { Host(opts, func(notation *utils.ExecOpts, artifact *Artifact, vhost *utils.VirtualHost) { notation.Exec("policy", "import", filepath.Join(NotationE2ETrustPolicyDir, "skip_trustpolicy.json")) // validate content, err := os.ReadFile(filepath.Join(NotationE2ETrustPolicyDir, TrustPolicyName)) Expect(err).NotTo(HaveOccurred()) notation.Exec("policy", "show").MatchContent(string(content)) }) }) It("should skip confirmation if existing policy is malformed", func() { Host(Opts(AddTrustPolicyOption("invalid_format_trustpolicy.json")), func(notation *utils.ExecOpts, artifact *Artifact, vhost *utils.VirtualHost) { policyFileName := "skip_trustpolicy.json" notation.Exec("policy", "import", filepath.Join(NotationE2ETrustPolicyDir, policyFileName)).MatchKeyWords(). MatchKeyWords("Trust policy configuration imported successfully.") // validate content, err := os.ReadFile(filepath.Join(NotationE2ETrustPolicyDir, policyFileName)) Expect(err).NotTo(HaveOccurred()) notation.Exec("policy", "show").MatchContent(string(content)) }) }) It("should confirm import", func() { Host(opts, func(notation *utils.ExecOpts, artifact *Artifact, vhost *utils.VirtualHost) { policyFileName := "skip_trustpolicy.json" notation.WithInput(strings.NewReader("Y\n")).Exec("policy", "import", filepath.Join(NotationE2ETrustPolicyDir, policyFileName)). MatchKeyWords("Trust policy configuration imported successfully.") // validate content, err := os.ReadFile(filepath.Join(NotationE2ETrustPolicyDir, policyFileName)) Expect(err).NotTo(HaveOccurred()) notation.Exec("policy", "show").MatchContent(string(content)) }) }) It("should confirm import by force", func() { Host(opts, func(notation *utils.ExecOpts, artifact *Artifact, vhost *utils.VirtualHost) { policyFileName := "skip_trustpolicy.json" notation.Exec("policy", "import", filepath.Join(NotationE2ETrustPolicyDir, policyFileName), "--force"). MatchKeyWords("Trust policy configuration imported successfully.") // validate content, err := os.ReadFile(filepath.Join(NotationE2ETrustPolicyDir, policyFileName)) Expect(err).NotTo(HaveOccurred()) notation.Exec("policy", "show").MatchContent(string(content)) }) }) }) }) notation-1.2.0/test/e2e/suite/command/sign.go000066400000000000000000000400071466375446100210560ustar00rootroot00000000000000// Copyright The Notary Project Authors. // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package command import ( "fmt" "path/filepath" "time" . "github.com/notaryproject/notation/test/e2e/internal/notation" "github.com/notaryproject/notation/test/e2e/internal/utils" . "github.com/notaryproject/notation/test/e2e/suite/common" . "github.com/onsi/ginkgo/v2" ) var _ = Describe("notation sign", func() { It("by digest", func() { Host(BaseOptions(), func(notation *utils.ExecOpts, artifact *Artifact, vhost *utils.VirtualHost) { notation.Exec("sign", artifact.ReferenceWithDigest()). MatchKeyWords(SignSuccessfully) OldNotation().WithDescription("verify by digest"). Exec("verify", artifact.ReferenceWithDigest()). MatchKeyWords(VerifySuccessfully) OldNotation().WithDescription("verify by tag"). Exec("verify", artifact.ReferenceWithTag()). MatchKeyWords(VerifySuccessfully) }) }) It("by digest with COSE format", func() { Host(BaseOptions(), func(notation *utils.ExecOpts, artifact *Artifact, vhost *utils.VirtualHost) { notation.Exec("sign", "--signature-format", "cose", artifact.ReferenceWithDigest()). MatchKeyWords(SignSuccessfully) OldNotation().WithDescription("verify by digest"). Exec("verify", artifact.ReferenceWithTag()). MatchKeyWords(VerifySuccessfully) OldNotation().WithDescription("verify by tag"). Exec("verify", artifact.ReferenceWithTag()). MatchKeyWords(VerifySuccessfully) }) }) It("by tag with JWS format", func() { Host(BaseOptions(), func(notation *utils.ExecOpts, artifact *Artifact, vhost *utils.VirtualHost) { notation.WithDescription("sign with JWS"). Exec("sign", artifact.ReferenceWithTag(), "--signature-format", "jws"). MatchKeyWords(SignSuccessfully) OldNotation().WithDescription("verify JWS signature"). Exec("verify", artifact.ReferenceWithTag()). MatchKeyWords(VerifySuccessfully) }) }) It("by tag with COSE signature format", func() { Host(BaseOptions(), func(notation *utils.ExecOpts, artifact *Artifact, vhost *utils.VirtualHost) { notation.WithDescription("sign with COSE"). Exec("sign", artifact.ReferenceWithTag(), "--signature-format", "cose"). MatchKeyWords(SignSuccessfully) OldNotation().WithDescription("verify COSE signature"). Exec("verify", artifact.ReferenceWithTag()). MatchKeyWords(VerifySuccessfully) }) }) It("with force-referrers-tag set", func() { Host(BaseOptions(), func(notation *utils.ExecOpts, artifact *Artifact, vhost *utils.VirtualHost) { notation.WithDescription("store signature with referrers tag schema"). Exec("sign", artifact.ReferenceWithDigest(), "--force-referrers-tag"). MatchKeyWords(SignSuccessfully) OldNotation().WithDescription("verify by tag schema"). Exec("verify", artifact.ReferenceWithDigest(), "-v"). MatchKeyWords(VerifySuccessfully) }) }) It("with force-referrers-tag set to false", func() { Host(BaseOptionsWithExperimental(), func(notation *utils.ExecOpts, artifact *Artifact, vhost *utils.VirtualHost) { notation.WithDescription("store signature with Referrers API"). Exec("sign", artifact.ReferenceWithDigest(), "--force-referrers-tag=false"). MatchKeyWords(SignSuccessfully) OldNotation(BaseOptionsWithExperimental()...).WithDescription("verify by referrers api"). Exec("verify", artifact.ReferenceWithDigest(), "--allow-referrers-api", "-v"). MatchKeyWords(VerifySuccessfully) }) }) It("with allow-referrers-api set", func() { Host(BaseOptionsWithExperimental(), func(notation *utils.ExecOpts, artifact *Artifact, vhost *utils.VirtualHost) { notation.WithDescription("store signature with Referrers API"). Exec("sign", artifact.ReferenceWithDigest(), "--allow-referrers-api"). MatchErrKeyWords( "Warning: This feature is experimental and may not be fully tested or completed and may be deprecated.", "Warning: flag '--allow-referrers-api' is deprecated and will be removed in future versions, use '--force-referrers-tag=false' instead.", ). MatchKeyWords(SignSuccessfully) OldNotation(BaseOptionsWithExperimental()...).WithDescription("verify by referrers api"). Exec("verify", artifact.ReferenceWithDigest(), "--allow-referrers-api", "-v"). MatchKeyWords(VerifySuccessfully) }) }) It("with allow-referrers-api set to false", func() { Host(BaseOptionsWithExperimental(), func(notation *utils.ExecOpts, artifact *Artifact, vhost *utils.VirtualHost) { notation.WithDescription("store signature with referrers tag schema"). Exec("sign", artifact.ReferenceWithDigest(), "--allow-referrers-api=false"). MatchErrKeyWords( "Warning: This feature is experimental and may not be fully tested or completed and may be deprecated.", "Warning: flag '--allow-referrers-api' is deprecated and will be removed in future versions.", ). MatchKeyWords(SignSuccessfully) OldNotation().WithDescription("verify by tag schema"). Exec("verify", artifact.ReferenceWithDigest(), "-v"). MatchKeyWords(VerifySuccessfully) }) }) It("with both force-referrers-tag and allow-referrers-api set", func() { Host(BaseOptionsWithExperimental(), func(notation *utils.ExecOpts, artifact *Artifact, vhost *utils.VirtualHost) { notation.WithDescription("store signature with Referrers API"). ExpectFailure(). Exec("sign", artifact.ReferenceWithDigest(), "--force-referrers-tag", "--allow-referrers-api"). MatchErrKeyWords( "Warning: This feature is experimental and may not be fully tested or completed and may be deprecated.", "[allow-referrers-api force-referrers-tag] were all set", ) }) }) It("with allow-referrers-api set and experimental off", func() { Host(BaseOptions(), func(notation *utils.ExecOpts, artifact *Artifact, vhost *utils.VirtualHost) { notation.WithDescription("store signature with Referrers API"). ExpectFailure(). Exec("sign", artifact.ReferenceWithDigest(), "--allow-referrers-api"). MatchErrKeyWords( "Error: flag(s) --allow-referrers-api in \"notation sign\" is experimental and not enabled by default.") }) }) It("with specific key", func() { Host(BaseOptions(), func(notation *utils.ExecOpts, artifact *Artifact, vhost *utils.VirtualHost) { const keyName = "sKey" notation.Exec("cert", "generate-test", keyName). MatchKeyWords(fmt.Sprintf("notation/localkeys/%s.crt", keyName)) notation.Exec("sign", "--key", keyName, artifact.ReferenceWithDigest()). MatchKeyWords(SignSuccessfully) // copy the generated cert file and create the new trust policy for verify signature with generated new key. OldNotation(AuthOption("", ""), AddTrustStoreOption(keyName, vhost.AbsolutePath(NotationDirName, LocalKeysDirName, keyName+".crt")), AddTrustPolicyOption("generate_test_trustpolicy.json"), ).Exec("verify", artifact.ReferenceWithTag()). MatchKeyWords(VerifySuccessfully) }) }) It("with expiry in 24h", func() { Host(BaseOptions(), func(notation *utils.ExecOpts, artifact *Artifact, vhost *utils.VirtualHost) { notation.Exec("sign", "--expiry", "24h", artifact.ReferenceWithDigest()). MatchKeyWords(SignSuccessfully) OldNotation().Exec("verify", artifact.ReferenceWithTag()). MatchKeyWords(VerifySuccessfully) }) }) It("with expiry in 2s", func() { Host(BaseOptions(), func(notation *utils.ExecOpts, artifact *Artifact, vhost *utils.VirtualHost) { notation.Exec("sign", "--expiry", "2s", artifact.ReferenceWithDigest()). MatchKeyWords(SignSuccessfully) // sleep to wait for expiry time.Sleep(2100 * time.Millisecond) OldNotation().ExpectFailure().Exec("verify", artifact.ReferenceWithDigest(), "-v"). MatchErrKeyWords("expiry validation failed."). MatchErrKeyWords("signature verification failed for all the signatures") }) }) It("by digest with oci layout", func() { HostWithOCILayout(BaseOptionsWithExperimental(), func(notation *utils.ExecOpts, ociLayout *OCILayout, vhost *utils.VirtualHost) { notation.Exec("sign", "--oci-layout", ociLayout.ReferenceWithDigest()). MatchKeyWords(SignSuccessfully) }) }) It("by digest with oci layout and COSE format", func() { HostWithOCILayout(BaseOptionsWithExperimental(), func(notation *utils.ExecOpts, ociLayout *OCILayout, vhost *utils.VirtualHost) { notation.Exec("sign", "--oci-layout", "--signature-format", "cose", ociLayout.ReferenceWithDigest()). MatchKeyWords(SignSuccessfully) }) }) It("by tag with oci layout", func() { HostWithOCILayout(BaseOptionsWithExperimental(), func(notation *utils.ExecOpts, ociLayout *OCILayout, vhost *utils.VirtualHost) { notation.Exec("sign", "--oci-layout", ociLayout.ReferenceWithDigest()). MatchKeyWords(SignSuccessfully) }) }) It("by tag with oci layout and COSE format", func() { HostWithOCILayout(BaseOptionsWithExperimental(), func(notation *utils.ExecOpts, ociLayout *OCILayout, vhost *utils.VirtualHost) { notation.Exec("sign", "--oci-layout", "--signature-format", "cose", ociLayout.ReferenceWithDigest()). MatchKeyWords(SignSuccessfully) }) }) It("by digest with oci layout but without experimental", func() { HostWithOCILayout(BaseOptions(), func(notation *utils.ExecOpts, ociLayout *OCILayout, vhost *utils.VirtualHost) { expectedErrMsg := "Error: flag(s) --oci-layout in \"notation sign\" is experimental and not enabled by default. To use, please set NOTATION_EXPERIMENTAL=1 environment variable\n" notation.ExpectFailure().Exec("sign", "--oci-layout", ociLayout.ReferenceWithDigest()). MatchErrContent(expectedErrMsg) }) }) It("with TLS by digest", func() { HostInGithubAction(BaseOptions(), func(notation *utils.ExecOpts, artifact *Artifact, vhost *utils.VirtualHost) { notation.Exec("sign", "-d", artifact.DomainReferenceWithDigest()). MatchKeyWords(SignSuccessfully). MatchErrKeyWords(HTTPSRequest). NoMatchErrKeyWords(HTTPRequest) OldNotation().Exec("verify", artifact.DomainReferenceWithDigest()). MatchKeyWords(VerifySuccessfully) }) }) It("with --insecure-registry by digest", func() { HostInGithubAction(BaseOptions(), func(notation *utils.ExecOpts, artifact *Artifact, vhost *utils.VirtualHost) { notation.Exec("sign", "-d", "--insecure-registry", artifact.DomainReferenceWithDigest()). MatchKeyWords(SignSuccessfully). MatchErrKeyWords(HTTPRequest). NoMatchErrKeyWords(HTTPSRequest) OldNotation().Exec("verify", artifact.DomainReferenceWithDigest()). MatchKeyWords(VerifySuccessfully) }) }) It("with timestamping", func() { Host(BaseOptions(), func(notation *utils.ExecOpts, artifact *Artifact, vhost *utils.VirtualHost) { notation.Exec("sign", "--timestamp-url", "http://rfc3161timestamp.globalsign.com/advanced", "--timestamp-root-cert", filepath.Join(NotationE2EConfigPath, "timestamp", "globalsignTSARoot.cer"), artifact.ReferenceWithDigest()). MatchKeyWords(SignSuccessfully) }) }) It("with timestamp-root-cert but no timestamp-url", func() { Host(BaseOptions(), func(notation *utils.ExecOpts, artifact *Artifact, vhost *utils.VirtualHost) { notation.ExpectFailure().Exec("sign", "--timestamp-root-cert", filepath.Join(NotationE2EConfigPath, "timestamp", "globalsignTSARoot.cer"), artifact.ReferenceWithDigest()). MatchErrKeyWords("Error: if any flags in the group [timestamp-url timestamp-root-cert] are set they must all be set; missing [timestamp-url]") }) }) It("with timestamp-url but no timestamp-root-cert", func() { Host(BaseOptions(), func(notation *utils.ExecOpts, artifact *Artifact, vhost *utils.VirtualHost) { notation.ExpectFailure().Exec("sign", "--timestamp-url", "http://rfc3161timestamp.globalsign.com/advanced", artifact.ReferenceWithDigest()). MatchErrKeyWords("Error: if any flags in the group [timestamp-url timestamp-root-cert] are set they must all be set; missing [timestamp-root-cert]") }) }) It("with timestamping and empty tsa server", func() { Host(BaseOptions(), func(notation *utils.ExecOpts, artifact *Artifact, vhost *utils.VirtualHost) { notation.ExpectFailure().Exec("sign", "--timestamp-url", "", "--timestamp-root-cert", filepath.Join(NotationE2EConfigPath, "timestamp", "globalsignTSARoot.cer"), artifact.ReferenceWithDigest()). MatchErrKeyWords("Error: timestamping: tsa url cannot be empty") }) }) It("with timestamping and empty tsa root cert", func() { Host(BaseOptions(), func(notation *utils.ExecOpts, artifact *Artifact, vhost *utils.VirtualHost) { notation.ExpectFailure().Exec("sign", "--timestamp-url", "dummy", "--timestamp-root-cert", "", artifact.ReferenceWithDigest()). MatchErrKeyWords("Error: timestamping: tsa root certificate path cannot be empty") }) }) It("with timestamping and invalid tsa server", func() { Host(BaseOptions(), func(notation *utils.ExecOpts, artifact *Artifact, vhost *utils.VirtualHost) { notation.ExpectFailure().Exec("sign", "--timestamp-url", "http://invalid.com", "--timestamp-root-cert", filepath.Join(NotationE2EConfigPath, "timestamp", "globalsignTSARoot.cer"), artifact.ReferenceWithDigest()). MatchErrKeyWords("Error: timestamp: Post \"http://invalid.com\""). MatchErrKeyWords("server misbehaving") }) }) It("with timestamping and invalid tsa root certificate", func() { Host(BaseOptions(), func(notation *utils.ExecOpts, artifact *Artifact, vhost *utils.VirtualHost) { notation.ExpectFailure().Exec("sign", "--timestamp-url", "http://timestamp.digicert.com", "--timestamp-root-cert", filepath.Join(NotationE2EConfigPath, "timestamp", "invalid.crt"), artifact.ReferenceWithDigest()). MatchErrKeyWords("Error: x509: malformed certificate") }) }) It("with timestamping and empty tsa root certificate file", func() { Host(BaseOptions(), func(notation *utils.ExecOpts, artifact *Artifact, vhost *utils.VirtualHost) { notation.ExpectFailure().Exec("sign", "--timestamp-url", "http://timestamp.digicert.com", "--timestamp-root-cert", filepath.Join(NotationE2EConfigPath, "timestamp", "Empty.txt"), artifact.ReferenceWithDigest()). MatchErrKeyWords("cannot find any certificate from"). MatchErrKeyWords("Expecting single x509 root certificate in PEM or DER format from the file") }) }) It("with timestamping and more than one certificates in tsa root certificate file", func() { Host(BaseOptions(), func(notation *utils.ExecOpts, artifact *Artifact, vhost *utils.VirtualHost) { notation.ExpectFailure().Exec("sign", "--timestamp-url", "http://timestamp.digicert.com", "--timestamp-root-cert", filepath.Join(NotationE2EConfigPath, "timestamp", "CertChain.pem"), artifact.ReferenceWithDigest()). MatchErrKeyWords("found more than one certificates"). MatchErrKeyWords("Expecting single x509 root certificate in PEM or DER format from the file") }) }) It("with timestamping and intermediate certificate file", func() { Host(BaseOptions(), func(notation *utils.ExecOpts, artifact *Artifact, vhost *utils.VirtualHost) { notation.ExpectFailure().Exec("sign", "--timestamp-url", "http://timestamp.digicert.com", "--timestamp-root-cert", filepath.Join(NotationE2EConfigPath, "timestamp", "intermediate.pem"), artifact.ReferenceWithDigest()). MatchErrKeyWords("failed to check root certificate with error: crypto/rsa: verification error") }) }) It("with timestamping and not self-issued certificate file", func() { Host(BaseOptions(), func(notation *utils.ExecOpts, artifact *Artifact, vhost *utils.VirtualHost) { notation.ExpectFailure().Exec("sign", "--timestamp-url", "http://timestamp.digicert.com", "--timestamp-root-cert", filepath.Join(NotationE2EConfigPath, "timestamp", "notSelfIssued.crt"), artifact.ReferenceWithDigest()). MatchErrKeyWords("is not a root certificate. Expecting single x509 root certificate in PEM or DER format from the file") }) }) }) notation-1.2.0/test/e2e/suite/command/verify.go000066400000000000000000000254041466375446100214260ustar00rootroot00000000000000// Copyright The Notary Project Authors. // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package command import ( "fmt" "path/filepath" . "github.com/notaryproject/notation/test/e2e/internal/notation" "github.com/notaryproject/notation/test/e2e/internal/utils" . "github.com/notaryproject/notation/test/e2e/suite/common" . "github.com/onsi/ginkgo/v2" ) var _ = Describe("notation verify", func() { It("by digest", func() { Host(BaseOptions(), func(notation *utils.ExecOpts, artifact *Artifact, vhost *utils.VirtualHost) { notation.Exec("sign", artifact.ReferenceWithDigest()). MatchKeyWords(SignSuccessfully) notation.Exec("verify", artifact.ReferenceWithDigest(), "-v"). MatchKeyWords(VerifySuccessfully) }) }) It("by tag", func() { Host(BaseOptions(), func(notation *utils.ExecOpts, artifact *Artifact, vhost *utils.VirtualHost) { notation.Exec("sign", artifact.ReferenceWithDigest()). MatchKeyWords(SignSuccessfully) notation.Exec("verify", artifact.ReferenceWithTag(), "-v"). MatchKeyWords(VerifySuccessfully) }) }) It("with debug log", func() { Host(BaseOptions(), func(notation *utils.ExecOpts, artifact *Artifact, vhost *utils.VirtualHost) { notation.Exec("sign", artifact.ReferenceWithDigest()). MatchKeyWords(SignSuccessfully) notation.Exec("verify", artifact.ReferenceWithDigest(), "-d"). // debug log message outputs to stderr MatchErrKeyWords( "Check verification level", fmt.Sprintf("Verify signature against artifact %s", artifact.Digest), "Validating cert chain", "Validating trust identity", "Validating expiry", "Validating authentic timestamp", "Validating revocation", ). MatchKeyWords(VerifySuccessfully) }) }) It("sign with --force-referrers-tag set", func() { Host(BaseOptions(), func(notation *utils.ExecOpts, artifact *Artifact, vhost *utils.VirtualHost) { notation.Exec("sign", "--force-referrers-tag", artifact.ReferenceWithDigest()). MatchKeyWords(SignSuccessfully) notation.Exec("verify", artifact.ReferenceWithDigest(), "-v"). MatchKeyWords(VerifySuccessfully) }) }) It("sign with --force-referrers-tag set to false", func() { Host(BaseOptions(), func(notation *utils.ExecOpts, artifact *Artifact, vhost *utils.VirtualHost) { notation.Exec("sign", "--force-referrers-tag=false", artifact.ReferenceWithDigest()). MatchKeyWords(SignSuccessfully) notation.Exec("verify", artifact.ReferenceWithDigest(), "-v"). MatchKeyWords(VerifySuccessfully) }) }) It("sign with --allow-referrers-api set", func() { Host(BaseOptionsWithExperimental(), func(notation *utils.ExecOpts, artifact *Artifact, vhost *utils.VirtualHost) { notation.Exec("sign", "--allow-referrers-api", artifact.ReferenceWithDigest()). MatchKeyWords(SignSuccessfully) notation.Exec("verify", artifact.ReferenceWithDigest(), "-v"). MatchKeyWords(VerifySuccessfully) notation.Exec("verify", artifact.ReferenceWithDigest(), "--allow-referrers-api", "-v"). MatchErrKeyWords( "Warning: This feature is experimental and may not be fully tested or completed and may be deprecated.", "Warning: flag '--allow-referrers-api' is deprecated and will be removed in future versions.", ). MatchKeyWords(VerifySuccessfully) }) }) It("sign with --allow-referrers-api set to false", func() { Host(BaseOptionsWithExperimental(), func(notation *utils.ExecOpts, artifact *Artifact, vhost *utils.VirtualHost) { notation.Exec("sign", "--allow-referrers-api=false", artifact.ReferenceWithDigest()). MatchKeyWords(SignSuccessfully) notation.Exec("verify", artifact.ReferenceWithDigest(), "-v"). MatchKeyWords(VerifySuccessfully) notation.Exec("verify", artifact.ReferenceWithDigest(), "--allow-referrers-api", "-v"). MatchErrKeyWords( "Warning: This feature is experimental and may not be fully tested or completed and may be deprecated.", "Warning: flag '--allow-referrers-api' is deprecated and will be removed in future versions.", ). MatchKeyWords(VerifySuccessfully) }) }) It("by digest with oci layout", func() { HostWithOCILayout(BaseOptionsWithExperimental(), func(notation *utils.ExecOpts, ociLayout *OCILayout, vhost *utils.VirtualHost) { notation.Exec("sign", "--oci-layout", ociLayout.ReferenceWithDigest()). MatchKeyWords(SignSuccessfully) experimentalMsg := "Warning: This feature is experimental and may not be fully tested or completed and may be deprecated. Report any issues to \"https://github/notaryproject/notation\"\n" notation.Exec("verify", "--oci-layout", "--scope", "local/e2e", ociLayout.ReferenceWithDigest()). MatchKeyWords(VerifySuccessfully). MatchErrKeyWords(experimentalMsg) }) }) It("by tag with oci layout and COSE format", func() { HostWithOCILayout(BaseOptionsWithExperimental(), func(notation *utils.ExecOpts, ociLayout *OCILayout, vhost *utils.VirtualHost) { notation.Exec("sign", "--oci-layout", "--signature-format", "cose", ociLayout.ReferenceWithTag()). MatchKeyWords(SignSuccessfully) experimentalMsg := "Warning: This feature is experimental and may not be fully tested or completed and may be deprecated. Report any issues to \"https://github/notaryproject/notation\"\n" notation.Exec("verify", "--oci-layout", "--scope", "local/e2e", ociLayout.ReferenceWithTag()). MatchKeyWords(VerifySuccessfully). MatchErrKeyWords(experimentalMsg) }) }) It("by digest with oci layout but without experimental", func() { HostWithOCILayout(BaseOptions(), func(notation *utils.ExecOpts, ociLayout *OCILayout, vhost *utils.VirtualHost) { expectedErrMsg := "Error: flag(s) --oci-layout,--scope in \"notation verify\" is experimental and not enabled by default. To use, please set NOTATION_EXPERIMENTAL=1 environment variable\n" notation.ExpectFailure().Exec("verify", "--oci-layout", "--scope", "local/e2e", ociLayout.ReferenceWithDigest()). MatchErrContent(expectedErrMsg) }) }) It("by digest with oci layout but missing scope", func() { HostWithOCILayout(BaseOptionsWithExperimental(), func(notation *utils.ExecOpts, ociLayout *OCILayout, vhost *utils.VirtualHost) { notation.Exec("sign", "--oci-layout", ociLayout.ReferenceWithDigest()). MatchKeyWords(SignSuccessfully) experimentalMsg := "Warning: This feature is experimental and may not be fully tested or completed and may be deprecated. Report any issues to \"https://github/notaryproject/notation\"\n" expectedErrMsg := "Error: if any flags in the group [oci-layout scope] are set they must all be set; missing [scope]" notation.ExpectFailure().Exec("verify", "--oci-layout", ociLayout.ReferenceWithDigest()). MatchErrKeyWords(experimentalMsg). MatchErrKeyWords(expectedErrMsg) }) }) It("with TLS by digest", func() { HostInGithubAction(BaseOptions(), func(notation *utils.ExecOpts, artifact *Artifact, vhost *utils.VirtualHost) { notation.Exec("sign", artifact.DomainReferenceWithDigest()). MatchKeyWords(SignSuccessfully) notation.Exec("verify", "-d", artifact.DomainReferenceWithDigest()). MatchKeyWords( VerifySuccessfully, ). MatchErrKeyWords(HTTPSRequest). NoMatchErrKeyWords(HTTPRequest) }) }) It("with --insecure-registry by digest", func() { HostInGithubAction(BaseOptions(), func(notation *utils.ExecOpts, artifact *Artifact, vhost *utils.VirtualHost) { notation.Exec("sign", artifact.DomainReferenceWithDigest()). MatchKeyWords(SignSuccessfully) notation.Exec("verify", "-d", "--insecure-registry", artifact.DomainReferenceWithDigest()). MatchKeyWords( VerifySuccessfully, ). MatchErrKeyWords(HTTPRequest). NoMatchErrKeyWords(HTTPSRequest) }) }) It("incorrect NOTATION_CONFIG path", func() { Host(BaseOptions(), func(notation *utils.ExecOpts, artifact *Artifact, vhost *utils.VirtualHost) { notation.Exec("sign", artifact.ReferenceWithDigest()). MatchKeyWords(SignSuccessfully) vhost.UpdateEnv(map[string]string{"NOTATION_CONFIG": "/not/exist"}) notation.ExpectFailure().Exec("verify", artifact.ReferenceWithDigest(), "-v"). MatchErrKeyWords("trust policy is not present") }) }) It("correct NOTATION_CONFIG path", func() { Host(BaseOptions(), func(notation *utils.ExecOpts, artifact *Artifact, vhost *utils.VirtualHost) { notation.Exec("sign", artifact.ReferenceWithDigest()). MatchKeyWords(SignSuccessfully) vhost.UpdateEnv(map[string]string{"NOTATION_CONFIG": vhost.AbsolutePath(NotationDirName)}) notation.Exec("verify", artifact.ReferenceWithDigest(), "-v"). MatchKeyWords(VerifySuccessfully) }) }) It("with timestamp verification disabled", func() { Host(BaseOptions(), func(notation *utils.ExecOpts, artifact *Artifact, vhost *utils.VirtualHost) { notation.Exec("sign", "--timestamp-url", "http://timestamp.digicert.com", "--timestamp-root-cert", filepath.Join(NotationE2EConfigPath, "timestamp", "DigiCertTSARootSHA384.cer"), artifact.ReferenceWithDigest()). MatchKeyWords(SignSuccessfully) notation.Exec("verify", artifact.ReferenceWithDigest(), "-v"). MatchKeyWords(VerifySuccessfully). MatchErrKeyWords("Timestamp verification disabled") }) }) It("with timestamp verification", func() { Host(TimestampOptions(""), func(notation *utils.ExecOpts, artifact *Artifact, vhost *utils.VirtualHost) { notation.Exec("sign", "--timestamp-url", "http://timestamp.digicert.com", "--timestamp-root-cert", filepath.Join(NotationE2EConfigPath, "timestamp", "DigiCertTSARootSHA384.cer"), artifact.ReferenceWithDigest()). MatchKeyWords(SignSuccessfully) notation.Exec("verify", artifact.ReferenceWithDigest(), "-v"). MatchKeyWords(VerifySuccessfully). MatchErrKeyWords("Performing timestamp verification...") }) }) It("with verifyTimestamp set as afterCertExpiry", func() { Host(TimestampOptions("afterCertExpiry"), func(notation *utils.ExecOpts, artifact *Artifact, vhost *utils.VirtualHost) { notation.Exec("sign", "--timestamp-url", "http://timestamp.digicert.com", "--timestamp-root-cert", filepath.Join(NotationE2EConfigPath, "timestamp", "DigiCertTSARootSHA384.cer"), artifact.ReferenceWithDigest()). MatchKeyWords(SignSuccessfully) notation.Exec("verify", artifact.ReferenceWithDigest(), "-v"). MatchKeyWords(VerifySuccessfully). MatchErrKeyWords("Timestamp verification disabled: verifyTimestamp is set to \\\"afterCertExpiry\\\" and signing cert chain unexpired") }) }) }) notation-1.2.0/test/e2e/suite/common/000077500000000000000000000000001466375446100174405ustar00rootroot00000000000000notation-1.2.0/test/e2e/suite/common/common.go000066400000000000000000000050051466375446100212570ustar00rootroot00000000000000// Copyright The Notary Project Authors. // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package common import ( "fmt" . "github.com/notaryproject/notation/test/e2e/internal/notation" ) const ( LoginSuccessfully = "Login Succeeded" LogoutSuccessfully = "Logout Succeeded" SignSuccessfully = "Successfully signed" VerifySuccessfully = "Successfully verified" VerifyFailed = "signature verification failed" ) var ( // HTTPRequest is the base URL for HTTP requests for testing // --insecure-registry flag HTTPRequest = fmt.Sprintf("http://%s", TestRegistry.DomainHost) // HTTPSRequest is the base URL for HTTPS requests for testing TLS request. HTTPSRequest = fmt.Sprintf("https://%s", TestRegistry.DomainHost) ) const ( // HeaderVerificationPlugin specifies the name of the verification plugin that should be used to verify the signature. HeaderVerificationPlugin = "io.cncf.notary.verificationPlugin" // HeaderVerificationPluginMinVersion specifies the minimum version of the verification plugin that should be used to verify the signature. HeaderVerificationPluginMinVersion = "io.cncf.notary.verificationPluginMinVersion" ) // Capability is a feature available in the plugin contract. type Capability string const ( // CapabilitySignatureGenerator is the name of the capability // for a plugin to support generating raw signatures. CapabilitySignatureGenerator Capability = "SIGNATURE_GENERATOR.RAW" // CapabilityEnvelopeGenerator is the name of the capability // for a plugin to support generating envelope signatures. CapabilityEnvelopeGenerator Capability = "SIGNATURE_GENERATOR.ENVELOPE" // CapabilityTrustedIdentityVerifier is the name of the // capability for a plugin to support verifying trusted identities. CapabilityTrustedIdentityVerifier Capability = "SIGNATURE_VERIFIER.TRUSTED_IDENTITY" // CapabilityRevocationCheckVerifier is the name of the // capability for a plugin to support verifying revocation checks. CapabilityRevocationCheckVerifier Capability = "SIGNATURE_VERIFIER.REVOCATION_CHECK" ) notation-1.2.0/test/e2e/suite/plugin/000077500000000000000000000000001466375446100174465ustar00rootroot00000000000000notation-1.2.0/test/e2e/suite/plugin/install.go000066400000000000000000000165401466375446100214510ustar00rootroot00000000000000// Copyright The Notary Project Authors. // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package plugin import ( "path/filepath" . "github.com/notaryproject/notation/test/e2e/internal/notation" "github.com/notaryproject/notation/test/e2e/internal/utils" . "github.com/onsi/ginkgo/v2" ) const ( PluginURL = "https://github.com/notaryproject/notation-action/raw/e2e-test-plugin/tests/plugin_binaries/notation-e2e-test-plugin_0.1.0_linux_amd64.tar.gz" PluginChecksum = "be8d035024d3a96afb4118af32f2e201f126c7254b02f7bcffb3e3149d744fd2" ) var _ = Describe("notation plugin install", func() { It("with missing file or url flag", func() { Host(nil, func(notation *utils.ExecOpts, _ *Artifact, vhost *utils.VirtualHost) { notation.ExpectFailure().Exec("plugin", "install", "."). MatchErrContent("Error: at least one of the flags in the group [file url] is required\n") }) }) It("with both file and url flags are set", func() { Host(nil, func(notation *utils.ExecOpts, _ *Artifact, vhost *utils.VirtualHost) { notation.ExpectFailure().Exec("plugin", "install", "--file", "--url", "."). MatchErrContent("Error: if any flags in the group [file url] are set none of the others can be; [file url] were all set\n") }) }) It("with missing plugin source", func() { Host(nil, func(notation *utils.ExecOpts, _ *Artifact, vhost *utils.VirtualHost) { notation.ExpectFailure().Exec("plugin", "install"). MatchErrContent("Error: missing plugin source location\n") }) }) It("with missing plugin file path", func() { Host(nil, func(notation *utils.ExecOpts, _ *Artifact, vhost *utils.VirtualHost) { notation.ExpectFailure().Exec("plugin", "install", "--file"). MatchErrContent("Error: missing plugin file path\n") }) }) It("with missing plugin URL", func() { Host(nil, func(notation *utils.ExecOpts, _ *Artifact, vhost *utils.VirtualHost) { notation.ExpectFailure().Exec("plugin", "install", "--url"). MatchErrContent("Error: missing plugin URL\n") }) }) It("with zip bomb single file exceeds 256 MiB size limit in zip format", func() { Host(nil, func(notation *utils.ExecOpts, _ *Artifact, vhost *utils.VirtualHost) { notation.ExpectFailure().Exec("plugin", "install", "--file", filepath.Join(NotationE2EMaliciousPluginArchivePath, "large_file_zip.zip"), "-v"). MatchErrContent("Error: plugin installation failed: total file size reached the 256 MiB size limit\n") }) }) It("with zip bomb single file exceeds 256 MiB size limit in tar.gz format", func() { Host(nil, func(notation *utils.ExecOpts, _ *Artifact, vhost *utils.VirtualHost) { notation.ExpectFailure().Exec("plugin", "install", "--file", filepath.Join(NotationE2EMaliciousPluginArchivePath, "large_file_tarGz.tar.gz"), "-v"). MatchErrContent("Error: plugin installation failed: total file size reached the 256 MiB size limit\n") }) }) It("with zip bomb total file size exceeds 256 MiB size limit", func() { Host(nil, func(notation *utils.ExecOpts, _ *Artifact, vhost *utils.VirtualHost) { notation.ExpectFailure().Exec("plugin", "install", "--file", filepath.Join(NotationE2EMaliciousPluginArchivePath, "zip_bomb.zip"), "-v"). MatchErrContent("Error: plugin installation failed: total file size reached the 256 MiB size limit\n") }) }) It("with zip slip", func() { Host(nil, func(notation *utils.ExecOpts, _ *Artifact, vhost *utils.VirtualHost) { notation.ExpectFailure().Exec("plugin", "install", "--file", filepath.Join(NotationE2EMaliciousPluginArchivePath, "zip_slip.zip"), "-v"). MatchErrContent("Error: plugin installation failed: file name in zip cannot contain '..', but found \"../../../../../../../../tmp/evil.txt\"\n") }) }) It("with valid plugin file path", func() { Host(nil, func(notation *utils.ExecOpts, _ *Artifact, vhost *utils.VirtualHost) { notation.Exec("plugin", "install", "--file", NotationE2EPluginTarGzPath, "-v"). MatchContent("Successfully installed plugin e2e-plugin, version 1.0.0\n") }) }) It("with plugin executable file path", func() { Host(nil, func(notation *utils.ExecOpts, _ *Artifact, vhost *utils.VirtualHost) { notation.Exec("plugin", "install", "--file", NotationE2EPluginPath). MatchContent("Successfully installed plugin e2e-plugin, version 1.0.0\n") }) }) It("with plugin already installed", func() { Host(nil, func(notation *utils.ExecOpts, _ *Artifact, vhost *utils.VirtualHost) { notation.Exec("plugin", "install", "--file", NotationE2EPluginTarGzPath). MatchContent("Successfully installed plugin e2e-plugin, version 1.0.0\n") notation.ExpectFailure().Exec("plugin", "install", "--file", NotationE2EPluginTarGzPath). MatchErrContent("Error: plugin installation failed: plugin e2e-plugin with version 1.0.0 already exists\n") }) }) It("with plugin already installed but force install", func() { Host(nil, func(notation *utils.ExecOpts, _ *Artifact, vhost *utils.VirtualHost) { notation.Exec("plugin", "install", "--file", NotationE2EPluginTarGzPath, "-v"). MatchContent("Successfully installed plugin e2e-plugin, version 1.0.0\n") notation.Exec("plugin", "install", "--file", NotationE2EPluginTarGzPath, "--force"). MatchContent("Successfully updated plugin e2e-plugin from version 1.0.0 to 1.0.0\n") }) }) It("with valid plugin URL", func() { Host(nil, func(notation *utils.ExecOpts, _ *Artifact, vhost *utils.VirtualHost) { notation.Exec("plugin", "install", "--url", PluginURL, "--sha256sum", PluginChecksum). MatchKeyWords("Successfully installed plugin e2e-test-plugin, version 0.1.0\n") }) }) It("with valid plugin URL but missing checksum", func() { Host(nil, func(notation *utils.ExecOpts, _ *Artifact, vhost *utils.VirtualHost) { notation.ExpectFailure().Exec("plugin", "install", "--url", PluginURL). MatchErrContent("Error: installing from URL requires non-empty SHA256 checksum of the plugin source\n") }) }) It("with valid plugin URL but mismatched SHA-256 checksum", func() { Host(nil, func(notation *utils.ExecOpts, _ *Artifact, vhost *utils.VirtualHost) { notation.ExpectFailure().Exec("plugin", "install", "--url", PluginURL, "--sha256sum", "abcd"). MatchErrContent("Error: plugin installation failed: plugin SHA-256 checksum does not match user input. Expecting abcd\n") }) }) It("with invalid plugin URL scheme", func() { Host(nil, func(notation *utils.ExecOpts, _ *Artifact, vhost *utils.VirtualHost) { notation.ExpectFailure().Exec("plugin", "install", "--url", "http://invalid", "--sha256sum", "abcd"). MatchErrContent("Error: failed to download plugin from URL: only the HTTPS scheme is supported, but got http\n") }) }) It("with invalid plugin URL", func() { Host(nil, func(notation *utils.ExecOpts, _ *Artifact, vhost *utils.VirtualHost) { notation.ExpectFailure().Exec("plugin", "install", "--url", "https://invalid", "--sha256sum", "abcd"). MatchErrKeyWords("failed to download plugin from URL https://invalid") }) }) }) notation-1.2.0/test/e2e/suite/plugin/list.go000066400000000000000000000031151466375446100207500ustar00rootroot00000000000000// Copyright The Notary Project Authors. // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package plugin import ( . "github.com/notaryproject/notation/test/e2e/internal/notation" "github.com/notaryproject/notation/test/e2e/internal/utils" . "github.com/onsi/ginkgo/v2" ) var _ = Describe("notation plugin list", func() { It("with empty result", func() { Host(nil, func(notation *utils.ExecOpts, _ *Artifact, vhost *utils.VirtualHost) { notation.Exec("plugin", "list"). MatchContent("NAME DESCRIPTION VERSION CAPABILITIES ERROR \n") }) }) It("with e2e-plugin installed", func() { Host(Opts(AddPlugin(NotationE2EPluginPath)), func(notation *utils.ExecOpts, _ *Artifact, vhost *utils.VirtualHost) { notation.Exec("plugin", "list"). MatchKeyWords("NAME", "e2e-plugin"). MatchKeyWords("DESCRIPTION", "The e2e-plugin is a Notation compatible plugin for Notation E2E test"). MatchKeyWords("VERSION", "1.0.0"). MatchKeyWords("CAPABILITIES", "[SIGNATURE_VERIFIER.TRUSTED_IDENTITY SIGNATURE_VERIFIER.REVOCATION_CHECK]"). MatchKeyWords("ERROR", "") }) }) }) notation-1.2.0/test/e2e/suite/plugin/plugin_test.go000066400000000000000000000014231466375446100223320ustar00rootroot00000000000000// Copyright The Notary Project Authors. // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package plugin_test import ( "testing" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" ) func TestPlugin(t *testing.T) { RegisterFailHandler(Fail) RunSpecs(t, "Plugin Suite") } notation-1.2.0/test/e2e/suite/plugin/sign.go000066400000000000000000000306271466375446100207450ustar00rootroot00000000000000// Copyright The Notary Project Authors. // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package plugin import ( "github.com/notaryproject/notation-core-go/signature/cose" . "github.com/notaryproject/notation/test/e2e/internal/notation" "github.com/notaryproject/notation/test/e2e/internal/utils" . "github.com/notaryproject/notation/test/e2e/suite/common" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" ) const ( TamperKeyID = "TAMPER_KEY_ID" TamperSignature = "TAMPER_SIGNATURE" TamperSignatureAlgorithm = "TAMPER_SIGNATURE_ALGORITHM" TamperCertificateChain = "TAMPER_CERTIFICATE_CHAIN" TamperSignatureEnvelope = "TAMPER_SIGNATURE_ENVELOPE" TamperSignatureEnvelopeType = "TAMPER_SIGNATURE_ENVELOPE_TYPE" TamperAnnotation = "TAMPER_ANNOTATION" ) var _ = Describe("notation plugin sign", func() { It("with JWS format and capability SIGNATURE_GENERATOR.RAW", func() { Host(BaseOptions(), func(notation *utils.ExecOpts, artifact *Artifact, vhost *utils.VirtualHost) { // setup plugin and plugin-key vhost.SetOption(AddPlugin(NotationE2EPluginPath)) notation.Exec("key", "add", "plugin-key", "--id", "key1", "--plugin", "e2e-plugin", "--plugin-config", string(CapabilitySignatureGenerator)+"=true"). MatchKeyWords("plugin-key") // run signing notation.Exec("sign", artifact.ReferenceWithDigest(), "--key", "plugin-key", "-d"). MatchErrKeyWords( "Plugin get-plugin-metadata request", "Plugin describe-key request", "Plugin generate-signature request", ). MatchKeyWords(SignSuccessfully) OldNotation().Exec("verify", artifact.ReferenceWithDigest()). MatchKeyWords(VerifySuccessfully) }) }) It("with COSE format and capability SIGNATURE_GENERATOR.RAW", func() { Host(BaseOptions(), func(notation *utils.ExecOpts, artifact *Artifact, vhost *utils.VirtualHost) { // setup plugin and plugin-key vhost.SetOption(AddPlugin(NotationE2EPluginPath)) notation.Exec("key", "add", "plugin-key", "--id", "key1", "--plugin", "e2e-plugin", "--plugin-config", string(CapabilitySignatureGenerator)+"=true"). MatchKeyWords("plugin-key") // run signing notation.Exec("sign", artifact.ReferenceWithDigest(), "--key", "plugin-key", "--signature-format", "cose", "-d"). MatchErrKeyWords( "Plugin get-plugin-metadata request", "Plugin describe-key request", "Plugin generate-signature request", ). MatchKeyWords(SignSuccessfully) OldNotation().Exec("verify", artifact.ReferenceWithDigest()). MatchKeyWords(VerifySuccessfully) }) }) It("with JWS format and capability SIGNATURE_GENERATOR.ENVELOPE", func() { Host(BaseOptions(), func(notation *utils.ExecOpts, artifact *Artifact, vhost *utils.VirtualHost) { // setup plugin and plugin-key vhost.SetOption(AddPlugin(NotationE2EPluginPath)) notation.Exec("key", "add", "plugin-key", "--id", "key1", "--plugin", "e2e-plugin", "--plugin-config", string(CapabilityEnvelopeGenerator)+"=true"). MatchKeyWords("plugin-key") // run signing notation.Exec("sign", artifact.ReferenceWithDigest(), "--key", "plugin-key", "-d"). MatchErrKeyWords( "Plugin get-plugin-metadata request", "Plugin generate-envelope request", ). MatchKeyWords(SignSuccessfully) OldNotation().Exec("verify", artifact.ReferenceWithDigest()). MatchKeyWords(VerifySuccessfully) }) }) It("with COSE format and capability SIGNATURE_GENERATOR.ENVELOPE", func() { Host(BaseOptions(), func(notation *utils.ExecOpts, artifact *Artifact, vhost *utils.VirtualHost) { // setup plugin and plugin-key vhost.SetOption(AddPlugin(NotationE2EPluginPath)) notation.Exec("key", "add", "plugin-key", "--id", "key1", "--plugin", "e2e-plugin", "--plugin-config", string(CapabilityEnvelopeGenerator)+"=true"). MatchKeyWords("plugin-key") // run signing notation.Exec("sign", artifact.ReferenceWithDigest(), "--key", "plugin-key", "--signature-format", "cose", "-d"). MatchErrKeyWords( "Plugin get-plugin-metadata request", "Plugin generate-envelope request", ). MatchKeyWords(SignSuccessfully) OldNotation().Exec("verify", artifact.ReferenceWithDigest()). MatchKeyWords(VerifySuccessfully) }) }) It("with capability SIGNATURE_GENERATOR.RAW and tampered KeyID", func() { Host(BaseOptions(), func(notation *utils.ExecOpts, artifact *Artifact, vhost *utils.VirtualHost) { // setup plugin and plugin-key vhost.SetOption(AddPlugin(NotationE2EPluginPath)) notation.Exec("key", "add", "plugin-key", "--id", "key1", "--plugin", "e2e-plugin", "--plugin-config", string(CapabilitySignatureGenerator)+"=true", "--plugin-config", TamperKeyID+"=key10"). MatchKeyWords("plugin-key") // run signing notation.ExpectFailure().Exec("sign", artifact.ReferenceWithDigest(), "--key", "plugin-key", "-d"). MatchErrKeyWords( "Plugin get-plugin-metadata request", "Plugin describe-key request", "Plugin generate-signature request", `keyID in generateSignature response "key10" does not match request "key1"`, ) }) }) It("with capability SIGNATURE_GENERATOR.RAW and tampered signature", func() { Host(BaseOptions(), func(notation *utils.ExecOpts, artifact *Artifact, vhost *utils.VirtualHost) { // setup plugin and plugin-key vhost.SetOption(AddPlugin(NotationE2EPluginPath)) notation.Exec("key", "add", "plugin-key", "--id", "key1", "--plugin", "e2e-plugin", "--plugin-config", string(CapabilitySignatureGenerator)+"=true", "--plugin-config", TamperSignature+"=invalid_sig"). MatchKeyWords("plugin-key") // run signing notation.ExpectFailure().Exec("sign", artifact.ReferenceWithDigest(), "--key", "plugin-key", "-d"). MatchErrKeyWords( "Plugin get-plugin-metadata request", "Plugin describe-key request", "Plugin generate-signature request", "generated signature failed verification: signature is invalid. Error: crypto/rsa: verification error", ) }) }) It("with capability SIGNATURE_GENERATOR.RAW and tampered signatureAlgorithm", func() { Skip("signatureAlgorithm returned by plugin is not verified.") Host(BaseOptions(), func(notation *utils.ExecOpts, artifact *Artifact, vhost *utils.VirtualHost) { // setup plugin and plugin-key vhost.SetOption(AddPlugin(NotationE2EPluginPath)) notation.Exec("key", "add", "plugin-key", "--id", "key1", "--plugin", "e2e-plugin", "--plugin-config", string(CapabilitySignatureGenerator)+"=true", "--plugin-config", TamperSignatureAlgorithm+"=invalid_alg"). MatchKeyWords("plugin-key") // run signing notation.ExpectFailure().Exec("sign", artifact.ReferenceWithDigest(), "--key", "plugin-key", "-d"). MatchErrKeyWords( "Plugin get-plugin-metadata request", "Plugin describe-key request", "Plugin generate-signature request", ) }) }) It("with capability SIGNATURE_GENERATOR.RAW and tampered certificate chain", func() { Host(BaseOptions(), func(notation *utils.ExecOpts, artifact *Artifact, vhost *utils.VirtualHost) { // setup plugin and plugin-key vhost.SetOption(AddPlugin(NotationE2EPluginPath)) notation.Exec("key", "add", "plugin-key", "--id", "key1", "--plugin", "e2e-plugin", "--plugin-config", string(CapabilitySignatureGenerator)+"=true", "--plugin-config", TamperCertificateChain+"=invalid_cert_chain"). MatchKeyWords("plugin-key") // run signing notation.ExpectFailure().Exec("sign", artifact.ReferenceWithDigest(), "--key", "plugin-key", "-d"). MatchErrKeyWords( "Plugin get-plugin-metadata request", "Plugin describe-key request", "Plugin generate-signature request", "x509: malformed certificate", ) }) }) It("with capability SIGNATURE_GENERATOR.ENVELOPE and tampered signature envelope", func() { Host(BaseOptions(), func(notation *utils.ExecOpts, artifact *Artifact, vhost *utils.VirtualHost) { // setup plugin and plugin-key vhost.SetOption(AddPlugin(NotationE2EPluginPath)) notation.Exec("key", "add", "plugin-key", "--id", "key1", "--plugin", "e2e-plugin", "--plugin-config", string(CapabilityEnvelopeGenerator)+"=true", "--plugin-config", TamperSignatureEnvelope+"={}"). MatchKeyWords("plugin-key") // run signing notation.ExpectFailure().Exec("sign", artifact.ReferenceWithDigest(), "--key", "plugin-key", "-d"). MatchErrKeyWords( "Plugin get-plugin-metadata request", "Plugin generate-envelope request", "Verifying signature envelope generated by the plugin", "generated signature failed verification: certificate chain is not present", ) }) }) It("with capability SIGNATURE_GENERATOR.ENVELOPE and tampered signature envelope type", func() { Host(BaseOptions(), func(notation *utils.ExecOpts, artifact *Artifact, vhost *utils.VirtualHost) { // setup plugin and plugin-key vhost.SetOption(AddPlugin(NotationE2EPluginPath)) notation.Exec("key", "add", "plugin-key", "--id", "key1", "--plugin", "e2e-plugin", "--plugin-config", string(CapabilityEnvelopeGenerator)+"=true", "--plugin-config", TamperSignatureEnvelopeType+"="+cose.MediaTypeEnvelope). MatchKeyWords("plugin-key") // run signing notation.ExpectFailure().Exec("sign", artifact.ReferenceWithDigest(), "--key", "plugin-key", "-d"). MatchErrKeyWords( "Plugin get-plugin-metadata request", "Plugin generate-envelope request", `signatureEnvelopeType in generateEnvelope response "application/cose" does not match request "application/jose+json"`, ) }) }) It("with capability SIGNATURE_GENERATOR.ENVELOPE and tampered annotation", func() { Host(BaseOptions(), func(notation *utils.ExecOpts, artifact *Artifact, vhost *utils.VirtualHost) { Skip("annotation returned by plugin is not processed") // setup plugin and plugin-key vhost.SetOption(AddPlugin(NotationE2EPluginPath)) notation.Exec("key", "add", "plugin-key", "--id", "key1", "--plugin", "e2e-plugin", "--plugin-config", string(CapabilityEnvelopeGenerator)+"=true", "--plugin-config", TamperAnnotation+"=k1=v1"). MatchKeyWords("plugin-key") // run signing notation.Exec("sign", artifact.ReferenceWithDigest(), "--key", "plugin-key", "-d"). MatchErrKeyWords( "Plugin get-plugin-metadata request", "Plugin generate-envelope request", ) // check signature annotation descriptors, err := artifact.SignatureDescriptors() Expect(err).ShouldNot(HaveOccurred()) // should have 1 signature Expect(len(descriptors)).Should(Equal(1)) // should have the annotation Expect(descriptors[0].Annotations).Should(HaveKeyWithValue("k1", "v1")) }) }) It("incorrect NOTATION_LIBEXEC path", func() { Host(BaseOptions(), func(notation *utils.ExecOpts, artifact *Artifact, vhost *utils.VirtualHost) { // setup incorrect NOTATION_LIBEXEC path vhost.SetOption(AddPlugin(NotationE2EPluginPath)) notation.Exec("key", "add", "plugin-key", "--id", "key1", "--plugin", "e2e-plugin", "--plugin-config", string(CapabilityEnvelopeGenerator)+"=true", "--plugin-config", TamperAnnotation+"=k1=v1"). MatchKeyWords("plugin-key") vhost.UpdateEnv(map[string]string{"NOTATION_LIBEXEC": "/not/exist"}) // run signing notation.ExpectFailure().Exec("sign", artifact.ReferenceWithDigest(), "--key", "plugin-key", "-d"). MatchErrKeyWords("no such file or directory") }) }) It("correct NOTATION_LIBEXEC path", func() { Host(BaseOptions(), func(notation *utils.ExecOpts, artifact *Artifact, vhost *utils.VirtualHost) { // setup incorrect NOTATION_LIBEXEC path vhost.SetOption(AddPlugin(NotationE2EPluginPath)) notation.Exec("key", "add", "plugin-key", "--id", "key1", "--plugin", "e2e-plugin", "--plugin-config", string(CapabilityEnvelopeGenerator)+"=true", "--plugin-config", TamperAnnotation+"=k1=v1"). MatchKeyWords("plugin-key") vhost.UpdateEnv(map[string]string{"NOTATION_LIBEXEC": vhost.AbsolutePath(NotationDirName)}) // run signing notation.Exec("sign", artifact.ReferenceWithDigest(), "--key", "plugin-key", "-d"). MatchKeyWords("Successfully signed") }) }) }) notation-1.2.0/test/e2e/suite/plugin/uninstall.go000066400000000000000000000027321466375446100220120ustar00rootroot00000000000000// Copyright The Notary Project Authors. // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package plugin import ( . "github.com/notaryproject/notation/test/e2e/internal/notation" "github.com/notaryproject/notation/test/e2e/internal/utils" . "github.com/onsi/ginkgo/v2" ) var _ = Describe("notation plugin uninstall", func() { It("with valid plugin name", func() { Host(nil, func(notation *utils.ExecOpts, _ *Artifact, vhost *utils.VirtualHost) { vhost.SetOption(AddPlugin(NotationE2EPluginPath)) notation.Exec("plugin", "uninstall", "--yes", "e2e-plugin"). MatchContent("Successfully uninstalled plugin e2e-plugin\n") }) }) It("with plugin does not exist", func() { Host(nil, func(notation *utils.ExecOpts, _ *Artifact, vhost *utils.VirtualHost) { notation.ExpectFailure().Exec("plugin", "uninstall", "--yes", "non-exist"). MatchErrContent("Error: unable to find plugin non-exist.\nTo view a list of installed plugins, use `notation plugin list`\n") }) }) }) notation-1.2.0/test/e2e/suite/plugin/verify.go000066400000000000000000000217351466375446100213110ustar00rootroot00000000000000// Copyright The Notary Project Authors. // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package plugin import ( "fmt" . "github.com/notaryproject/notation/test/e2e/internal/notation" "github.com/notaryproject/notation/test/e2e/internal/utils" . "github.com/notaryproject/notation/test/e2e/suite/common" . "github.com/onsi/ginkgo/v2" ) var _ = Describe("notation plugin verify", func() { It("with basic case", func() { Host(BaseOptions(), func(notation *utils.ExecOpts, artifact *Artifact, vhost *utils.VirtualHost) { // setup plugin and plugin-key vhost.SetOption(AddPlugin(NotationE2EPluginPath)) notation.Exec("key", "add", "plugin-key", "--id", "key1", "--plugin", "e2e-plugin", // add pluginConfig to enable generating envelope capability and update extended attribute "--plugin-config", fmt.Sprintf("%s=true", CapabilityEnvelopeGenerator), // specify verification plugin is e2e-plugin "--plugin-config", fmt.Sprintf("%s=e2e-plugin", HeaderVerificationPlugin)). MatchKeyWords("plugin-key") // run signing notation.Exec("sign", artifact.ReferenceWithDigest(), "--key", "plugin-key", "-d"). MatchErrKeyWords( "Plugin get-plugin-metadata request", "Plugin generate-envelope request", ). MatchKeyWords(SignSuccessfully) notation.Exec("verify", artifact.ReferenceWithDigest(), "-d"). MatchErrKeyWords( "Plugin verify-signature request", "Plugin verify-signature response", `{\"verificationResults\":{\"SIGNATURE_VERIFIER.REVOCATION_CHECK\":{\"success\":true},\"SIGNATURE_VERIFIER.TRUSTED_IDENTITY\":{\"success\":true}},\"processedAttributes\":null}`). MatchKeyWords(VerifySuccessfully) }) }) It("with plugin revocation check failed", func() { Host(BaseOptions(), func(notation *utils.ExecOpts, artifact *Artifact, vhost *utils.VirtualHost) { // setup plugin and plugin-key vhost.SetOption(AddPlugin(NotationE2EPluginPath)) notation.Exec("key", "add", "plugin-key", "--id", "key1", "--plugin", "e2e-plugin", // add pluginConfig to enable generating envelope capability and update extended attribute "--plugin-config", fmt.Sprintf("%s=true", CapabilityEnvelopeGenerator), // specify verification plugin is e2e-plugin "--plugin-config", fmt.Sprintf("%s=e2e-plugin", HeaderVerificationPlugin)). MatchKeyWords("plugin-key") // run signing notation.Exec("sign", artifact.ReferenceWithDigest(), "--key", "plugin-key", "-d"). MatchErrKeyWords( "Plugin get-plugin-metadata request", "Plugin generate-envelope request", ). MatchKeyWords(SignSuccessfully) notation.ExpectFailure().Exec("verify", artifact.ReferenceWithDigest(), "-d", // set revocation check failed for plugin "--plugin-config", fmt.Sprintf("%s=failed", CapabilityRevocationCheckVerifier), ). MatchErrKeyWords( "Plugin verify-signature request", "Plugin verify-signature response", `revocation check by verification plugin \"e2e-plugin\" failed with reason \"revocation check failed\"`, VerifyFailed) }) }) It("with plugin trusted identity check failed", func() { Host(BaseOptions(), func(notation *utils.ExecOpts, artifact *Artifact, vhost *utils.VirtualHost) { // setup plugin and plugin-key vhost.SetOption(AddPlugin(NotationE2EPluginPath)) notation.Exec("key", "add", "plugin-key", "--id", "key1", "--plugin", "e2e-plugin", // add pluginConfig to enable generating envelope capability and update extended attribute "--plugin-config", fmt.Sprintf("%s=true", CapabilityEnvelopeGenerator), // specify verification plugin is e2e-plugin "--plugin-config", fmt.Sprintf("%s=e2e-plugin", HeaderVerificationPlugin)). MatchKeyWords("plugin-key") // run signing notation.Exec("sign", artifact.ReferenceWithDigest(), "--key", "plugin-key", "-d"). MatchErrKeyWords( "Plugin get-plugin-metadata request", "Plugin generate-envelope request", ). MatchKeyWords(SignSuccessfully) notation.ExpectFailure().Exec("verify", artifact.ReferenceWithDigest(), "-d", // set trusted identity check failed for plugin "--plugin-config", fmt.Sprintf("%s=failed", CapabilityTrustedIdentityVerifier), ). MatchErrKeyWords( "Plugin verify-signature request", "Plugin verify-signature response", `trusted identify verification by plugin \"e2e-plugin\" failed with reason \"trusted identity check failed\"`, VerifyFailed) }) }) It("with plugin minimum version 1.0.0", func() { Host(BaseOptions(), func(notation *utils.ExecOpts, artifact *Artifact, vhost *utils.VirtualHost) { // setup plugin and plugin-key vhost.SetOption(AddPlugin(NotationE2EPluginPath)) notation.Exec("key", "add", "plugin-key", "--id", "key1", "--plugin", "e2e-plugin", // add pluginConfig to enable generating envelope capability and update extended attribute "--plugin-config", fmt.Sprintf("%s=true", CapabilityEnvelopeGenerator), // specify verification plugin is e2e-plugin "--plugin-config", fmt.Sprintf("%s=e2e-plugin", HeaderVerificationPlugin), // specify verification plugin minimum version "--plugin-config", fmt.Sprintf("%s=1.0.0", HeaderVerificationPluginMinVersion)). MatchKeyWords("plugin-key") // run signing notation.Exec("sign", artifact.ReferenceWithDigest(), "--key", "plugin-key", "-d"). MatchErrKeyWords( "Plugin get-plugin-metadata request", "Plugin generate-envelope request", ). MatchKeyWords(SignSuccessfully) notation.Exec("verify", artifact.ReferenceWithDigest(), "-d"). MatchErrKeyWords( "Plugin verify-signature request", "Plugin verify-signature response", `{\"verificationResults\":{\"SIGNATURE_VERIFIER.REVOCATION_CHECK\":{\"success\":true},\"SIGNATURE_VERIFIER.TRUSTED_IDENTITY\":{\"success\":true}},\"processedAttributes\":null}`). MatchKeyWords(VerifySuccessfully) }) }) It("with plugin minimum version 1.0.11", func() { Host(BaseOptions(), func(notation *utils.ExecOpts, artifact *Artifact, vhost *utils.VirtualHost) { // setup plugin and plugin-key vhost.SetOption(AddPlugin(NotationE2EPluginPath)) notation.Exec("key", "add", "plugin-key", "--id", "key1", "--plugin", "e2e-plugin", // add pluginConfig to enable generating envelope capability and update extended attribute "--plugin-config", fmt.Sprintf("%s=true", CapabilityEnvelopeGenerator), // specify verification plugin is e2e-plugin "--plugin-config", fmt.Sprintf("%s=e2e-plugin", HeaderVerificationPlugin), // specify verification plugin minimum version "--plugin-config", fmt.Sprintf("%s=1.0.11", HeaderVerificationPluginMinVersion)). MatchKeyWords("plugin-key") // run signing notation.Exec("sign", artifact.ReferenceWithDigest(), "--key", "plugin-key", "-d"). MatchErrKeyWords( "Plugin get-plugin-metadata request", "Plugin generate-envelope request", ). MatchKeyWords(SignSuccessfully) notation.ExpectFailure().Exec("verify", artifact.ReferenceWithDigest(), "-d"). MatchErrKeyWords( "found plugin e2e-plugin with version 1.0.0 but signature verification needs plugin version greater than or equal to 1.0.1", VerifyFailed, ) }) }) It("with plugin minimum version 0.0.1", func() { Host(BaseOptions(), func(notation *utils.ExecOpts, artifact *Artifact, vhost *utils.VirtualHost) { // setup plugin and plugin-key vhost.SetOption(AddPlugin(NotationE2EPluginPath)) notation.Exec("key", "add", "plugin-key", "--id", "key1", "--plugin", "e2e-plugin", // add pluginConfig to enable generating envelope capability and update extended attribute "--plugin-config", fmt.Sprintf("%s=true", CapabilityEnvelopeGenerator), // specify verification plugin is e2e-plugin "--plugin-config", fmt.Sprintf("%s=e2e-plugin", HeaderVerificationPlugin), // specify verification plugin minimum version "--plugin-config", fmt.Sprintf("%s=0.0.1", HeaderVerificationPluginMinVersion)). MatchKeyWords("plugin-key") // run signing notation.Exec("sign", artifact.ReferenceWithDigest(), "--key", "plugin-key", "-d"). MatchErrKeyWords( "Plugin get-plugin-metadata request", "Plugin generate-envelope request", ). MatchKeyWords(SignSuccessfully) notation.Exec("verify", artifact.ReferenceWithDigest(), "-d"). MatchErrKeyWords( "Plugin verify-signature request", "Plugin verify-signature response", `{\"verificationResults\":{\"SIGNATURE_VERIFIER.REVOCATION_CHECK\":{\"success\":true},\"SIGNATURE_VERIFIER.TRUSTED_IDENTITY\":{\"success\":true}},\"processedAttributes\":null}`). MatchKeyWords(VerifySuccessfully) }) }) }) notation-1.2.0/test/e2e/suite/scenario/000077500000000000000000000000001466375446100177535ustar00rootroot00000000000000notation-1.2.0/test/e2e/suite/scenario/quickstart.go000066400000000000000000000072441466375446100225030ustar00rootroot00000000000000// Copyright The Notary Project Authors. // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package scenario_test import ( "fmt" . "github.com/notaryproject/notation/test/e2e/internal/notation" "github.com/notaryproject/notation/test/e2e/internal/utils" "github.com/notaryproject/notation/test/e2e/internal/utils/validator" . "github.com/onsi/ginkgo/v2" ) // quickstart doc: https://notaryproject.dev/docs/quickstart/ var _ = Describe("notation quickstart E2E test", Ordered, func() { var vhost *utils.VirtualHost var artifact *Artifact var artifact2 *Artifact var notation *utils.ExecOpts BeforeAll(func() { var err error // setup host vhost, err = utils.NewVirtualHost(NotationBinPath, CreateNotationDirOption()) if err != nil { panic(err) } vhost.SetOption(AuthOption("", "")) notation = vhost.Executor // add an image to the OCI-compatible registry artifact = GenerateArtifact("", "") artifact2 = GenerateArtifact("", "") }) It("list the signatures associated with the container image", func() { notation.Exec("ls", artifact.ReferenceWithTag()). MatchKeyWords("has no associated signature") }) It("generate a test key and self-signed certificate", func() { notation.Exec("cert", "generate-test", "--default", "wabbit-networks.io"). MatchKeyWords( "Successfully added wabbit-networks.io.crt", "wabbit-networks.io: added to the key list", "wabbit-networks.io: mark as default signing key") notation.Exec("key", "ls"). MatchKeyWords( "notation/localkeys/wabbit-networks.io.key", "notation/localkeys/wabbit-networks.io.crt", ) notation.Exec("cert", "ls"). MatchKeyWords( "ca", "wabbit-networks.io", "wabbit-networks.io.crt", ) }) It("sign the container image with jws format (by default)", func() { notation.Exec("sign", artifact.ReferenceWithDigest()). MatchContent(fmt.Sprintf("Successfully signed %s\n", artifact.ReferenceWithDigest())) notation.Exec("ls", artifact.ReferenceWithDigest()). MatchKeyWords(fmt.Sprintf("%s\n└── application/vnd.cncf.notary.signature\n └── sha256:", artifact.ReferenceWithDigest())) }) It("sign the container image with cose format", func() { notation.Exec("sign", "--signature-format", "cose", artifact2.ReferenceWithDigest()). MatchContent(fmt.Sprintf("Successfully signed %s\n", artifact2.ReferenceWithDigest())) notation.Exec("ls", artifact2.ReferenceWithDigest()). MatchKeyWords(fmt.Sprintf("%s\n└── application/vnd.cncf.notary.signature\n └── sha256:", artifact2.ReferenceWithDigest())) }) It("Create a trust policy", func() { vhost.SetOption(AddTrustPolicyOption("quickstart_trustpolicy.json")) validator.CheckFileExist(vhost.AbsolutePath(NotationDirName, TrustPolicyName)) }) It("Verify the container image with jws format", func() { notation.Exec("verify", artifact.ReferenceWithDigest()). MatchKeyWords(fmt.Sprintf("Successfully verified signature for %s\n", artifact.ReferenceWithDigest())) }) It("Verify the container image with cose format", func() { notation.Exec("verify", artifact2.ReferenceWithDigest()). MatchContent(fmt.Sprintf("Successfully verified signature for %s\n", artifact2.ReferenceWithDigest())) }) }) notation-1.2.0/test/e2e/suite/scenario/scenario_test.go000066400000000000000000000014311466375446100231430ustar00rootroot00000000000000// Copyright The Notary Project Authors. // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package scenario_test import ( "testing" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" ) func TestScenario(t *testing.T) { RegisterFailHandler(Fail) RunSpecs(t, "Scenario Suite") } notation-1.2.0/test/e2e/suite/trustpolicy/000077500000000000000000000000001466375446100205515ustar00rootroot00000000000000notation-1.2.0/test/e2e/suite/trustpolicy/multi_statements.go000066400000000000000000000070521466375446100245050ustar00rootroot00000000000000// Copyright The Notary Project Authors. // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package trustpolicy import ( . "github.com/notaryproject/notation/test/e2e/internal/notation" "github.com/notaryproject/notation/test/e2e/internal/utils" . "github.com/notaryproject/notation/test/e2e/suite/common" . "github.com/onsi/ginkgo/v2" ) var _ = Describe("notation trust policy multi-statements test", func() { It("multiple statements with the same registryScope", func() { Host(BaseOptions(), func(notation *utils.ExecOpts, _ *Artifact, vhost *utils.VirtualHost) { artifact := GenerateArtifact("", "test-repo8") // update trustpolicy.json vhost.SetOption(AddTrustPolicyOption("multi_statements_with_the_same_registry_scope_trustpolicy.json")) // test localhost:5000/test-repo notation.Exec("sign", artifact.ReferenceWithDigest()).MatchKeyWords(SignSuccessfully) notation.ExpectFailure().Exec("verify", artifact.ReferenceWithDigest()). MatchErrContent("Error: registry scope \"localhost:5000/test-repo8\" is present in multiple trust policy statements, one registry scope value can only be associated with one statement\n") }) }) It("multiple statements with wildcard registry scope", func() { Host(BaseOptions(), func(notation *utils.ExecOpts, _ *Artifact, vhost *utils.VirtualHost) { artifact := GenerateArtifact("e2e-valid-signature", "test-repo9") // update trustpolicy.json vhost.SetOption(AddTrustPolicyOption("multi_statements_with_wildcard_registry_scope_trustpolicy.json")) // test localhost:5000/test-repo notation.Exec("sign", artifact.ReferenceWithDigest()).MatchKeyWords(SignSuccessfully) notation.Exec("verify", artifact.ReferenceWithDigest()). MatchKeyWords(VerifySuccessfully) }) }) It("multiple statements with the same name", func() { Host(BaseOptions(), func(notation *utils.ExecOpts, _ *Artifact, vhost *utils.VirtualHost) { artifact := GenerateArtifact("", "test-repo10") // update trustpolicy.json vhost.SetOption(AddTrustPolicyOption("multi_statements_with_the_same_name_trustpolicy.json")) // test localhost:5000/test-repo notation.Exec("sign", artifact.ReferenceWithDigest()).MatchKeyWords(SignSuccessfully) notation.ExpectFailure().Exec("verify", artifact.ReferenceWithDigest()). MatchErrContent("Error: multiple trust policy statements use the same name \"e2e\", statement names must be unique\n") }) }) It("multiple statements with multi-wildcard registry scopes", func() { Host(BaseOptions(), func(notation *utils.ExecOpts, artifact *Artifact, vhost *utils.VirtualHost) { // update trustpolicy.json vhost.SetOption(AddTrustPolicyOption("multi_statements_with_multi_wildcard_registry_scope_trustpolicy.json")) // test localhost:5000/test-repo notation.Exec("sign", artifact.ReferenceWithDigest()).MatchKeyWords(SignSuccessfully) notation.ExpectFailure().Exec("verify", artifact.ReferenceWithDigest()). MatchErrContent("Error: registry scope \"*\" is present in multiple trust policy statements, one registry scope value can only be associated with one statement\n") }) }) }) notation-1.2.0/test/e2e/suite/trustpolicy/registry_scope.go000066400000000000000000000151361466375446100241470ustar00rootroot00000000000000// Copyright The Notary Project Authors. // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package trustpolicy import ( "fmt" . "github.com/notaryproject/notation/test/e2e/internal/notation" "github.com/notaryproject/notation/test/e2e/internal/utils" . "github.com/notaryproject/notation/test/e2e/suite/common" . "github.com/onsi/ginkgo/v2" ) // trustPolicyLink is a tutorial link for creating Notation's trust policy. const trustPolicyLink = "https://notaryproject.dev/docs/quickstart/#create-a-trust-policy" var _ = Describe("notation trust policy registryScope test", func() { It("empty registryScope", func() { Host(BaseOptions(), func(notation *utils.ExecOpts, artifact *Artifact, vhost *utils.VirtualHost) { // update trustpolicy.json vhost.SetOption(AddTrustPolicyOption("empty_registry_scope_trustpolicy.json")) // test localhost:5000/test-repo OldNotation().Exec("sign", artifact.ReferenceWithDigest()).MatchKeyWords(SignSuccessfully) notation.ExpectFailure().Exec("verify", artifact.ReferenceWithDigest()). MatchErrKeyWords("trust policy statement \"e2e\" has zero registry scopes") }) }) It("malformed registryScope", func() { Host(BaseOptions(), func(notation *utils.ExecOpts, artifact *Artifact, vhost *utils.VirtualHost) { // update trustpolicy.json vhost.SetOption(AddTrustPolicyOption("malformed_registry_scope_trustpolicy.json")) OldNotation().Exec("sign", artifact.ReferenceWithDigest()).MatchKeyWords(SignSuccessfully) notation.ExpectFailure().Exec("verify", artifact.ReferenceWithDigest()). MatchErrKeyWords(`registry scope "localhost:5000\\test-repo" is not valid, make sure it is a fully qualified repository without the scheme, protocol or tag. For example domain.com/my/repository or a local scope like local/myOCILayout`) }) }) It("registryScope with a repository", func() { Host(BaseOptions(), func(notation *utils.ExecOpts, _ *Artifact, vhost *utils.VirtualHost) { // update trustpolicy.json vhost.SetOption(AddTrustPolicyOption("registry_scope_trustpolicy.json")) // generate an artifact with given repository name artifact := GenerateArtifact("", "test-repo") // test localhost:5000/test-repo notation.Exec("sign", artifact.ReferenceWithDigest()).MatchKeyWords(SignSuccessfully) notation.Exec("verify", artifact.ReferenceWithDigest()).MatchKeyWords(VerifySuccessfully) }) }) It("registryScope with multiple repositories", func() { Host(BaseOptions(), func(notation *utils.ExecOpts, _ *Artifact, vhost *utils.VirtualHost) { // update trustpolicy.json vhost.SetOption(AddTrustPolicyOption("multiple_registry_scope_trustpolicy.json")) // generate an artifact with given repository name artifact2 := GenerateArtifact("", "test-repo2") artifact3 := GenerateArtifact("", "test-repo3") // test localhost:5000/test-repo2 notation.Exec("sign", artifact2.ReferenceWithDigest()).MatchKeyWords(SignSuccessfully) notation.Exec("verify", artifact2.ReferenceWithDigest()).MatchKeyWords(VerifySuccessfully) // test localhost:5000/test-repo3 notation.Exec("sign", artifact3.ReferenceWithDigest()).MatchKeyWords(SignSuccessfully) notation.Exec("verify", artifact3.ReferenceWithDigest()).MatchKeyWords(VerifySuccessfully) }) }) It("registryScope with any(*) repository", func() { Host(BaseOptions(), func(notation *utils.ExecOpts, _ *Artifact, vhost *utils.VirtualHost) { // update trustpolicy.json vhost.SetOption(AddTrustPolicyOption("any_registry_scope_trust_policy.json")) // generate an artifact with given repository name artifact4 := GenerateArtifact("", "test-repo4") artifact5 := GenerateArtifact("", "test-repo5") // test localhost:5000/test-repo4 notation.Exec("sign", artifact4.ReferenceWithDigest()).MatchKeyWords(SignSuccessfully) notation.Exec("verify", artifact4.ReferenceWithDigest()).MatchKeyWords(VerifySuccessfully) // test localhost:5000/test-repo5 notation.Exec("sign", artifact5.ReferenceWithDigest()).MatchKeyWords(SignSuccessfully) notation.Exec("verify", artifact5.ReferenceWithDigest()).MatchKeyWords(VerifySuccessfully) }) }) It("overlapped registryScope", func() { Host(BaseOptions(), func(notation *utils.ExecOpts, _ *Artifact, vhost *utils.VirtualHost) { // update trustpolicy.json vhost.SetOption(AddTrustPolicyOption("overlapped_registry_scope_trustpolicy.json")) artifact := GenerateArtifact("", "test-repo6") // test localhost:5000/test-repo OldNotation().Exec("sign", artifact.ReferenceWithDigest()).MatchKeyWords(SignSuccessfully) notation.ExpectFailure().Exec("verify", artifact.ReferenceWithDigest()). MatchErrKeyWords("registry scope \"localhost:5000/test-repo6\" is present in multiple trust policy statements") }) }) It("wildcard plus specific repo registryScope", func() { Host(BaseOptions(), func(notation *utils.ExecOpts, _ *Artifact, vhost *utils.VirtualHost) { // update trustpolicy.json vhost.SetOption(AddTrustPolicyOption("wildcard_plus_other_registry_scope_trustpolicy.json")) artifact := GenerateArtifact("", "test-repo7") // test localhost:5000/test-repo OldNotation().Exec("sign", artifact.ReferenceWithDigest()).MatchKeyWords(SignSuccessfully) notation.ExpectFailure().Exec("verify", artifact.ReferenceWithDigest()). MatchErrKeyWords("trust policy statement \"e2e\" uses wildcard registry scope '*', a wildcard scope cannot be used in conjunction with other scope values") }) }) It("invalid registryScope", func() { Host(BaseOptions(), func(notation *utils.ExecOpts, artifact *Artifact, vhost *utils.VirtualHost) { // update trustpolicy.json vhost.SetOption(AddTrustPolicyOption("invalid_registry_scope_trustpolicy.json")) // test localhost:5000/test-repo OldNotation().Exec("sign", artifact.ReferenceWithDigest()).MatchKeyWords(SignSuccessfully) notation.ExpectFailure().Exec("verify", artifact.ReferenceWithDigest()). MatchErrContent(fmt.Sprintf("Error: signature verification failed: artifact %q has no applicable trust policy statement. Trust policy applicability for a given artifact is determined by registryScopes. To create a trust policy, see: %s\n", artifact.ReferenceWithDigest(), trustPolicyLink)) }) }) }) notation-1.2.0/test/e2e/suite/trustpolicy/trust_store.go000066400000000000000000000111741466375446100235010ustar00rootroot00000000000000// Copyright The Notary Project Authors. // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package trustpolicy import ( "path/filepath" . "github.com/notaryproject/notation/test/e2e/internal/notation" "github.com/notaryproject/notation/test/e2e/internal/utils" . "github.com/notaryproject/notation/test/e2e/suite/common" . "github.com/onsi/ginkgo/v2" ) var _ = Describe("notation trust policy trust store test", func() { It("unset trust store", func() { Host(BaseOptions(), func(notation *utils.ExecOpts, _ *Artifact, vhost *utils.VirtualHost) { vhost.SetOption(AddTrustPolicyOption("unset_trust_store_trustpolicy.json")) artifact := GenerateArtifact("e2e-valid-signature", "") notation.ExpectFailure().Exec("verify", artifact.ReferenceWithDigest(), "-v"). MatchErrKeyWords(`trust policy statement "e2e" is either missing trust stores or trusted identities, both must be specified`) }) }) It("invalid trust store", func() { Host(BaseOptions(), func(notation *utils.ExecOpts, _ *Artifact, vhost *utils.VirtualHost) { vhost.SetOption(AddTrustPolicyOption("invalid_trust_store_trustpolicy.json")) artifact := GenerateArtifact("e2e-valid-signature", "") notation.ExpectFailure().Exec("verify", artifact.ReferenceWithDigest(), "-v"). MatchErrKeyWords("the trust store \"invalid_store\" of type \"ca\" does not exist") }) }) It("malformed trust store", func() { Host(BaseOptions(), func(notation *utils.ExecOpts, _ *Artifact, vhost *utils.VirtualHost) { vhost.SetOption(AddTrustPolicyOption("malformed_trust_store_trustpolicy.json")) artifact := GenerateArtifact("e2e-valid-signature", "") notation.ExpectFailure().Exec("verify", artifact.ReferenceWithDigest(), "-v"). MatchErrKeyWords(`trust policy statement "e2e" uses an unsupported trust store name "" in trust store value "ca:". Named store name needs to follow [a-zA-Z0-9_.-]+ format`) }) }) It("wildcard (malformed) trust store", func() { Host(BaseOptions(), func(notation *utils.ExecOpts, _ *Artifact, vhost *utils.VirtualHost) { vhost.SetOption(AddTrustPolicyOption("wildcard_trust_store_trustpolicy.json")) artifact := GenerateArtifact("e2e-valid-signature", "") notation.ExpectFailure().Exec("verify", artifact.ReferenceWithDigest(), "-v"). MatchErrKeyWords(`trust policy statement "e2e" has malformed trust store value "*". The required format is :`) }) }) It("multiple trust stores", func() { Host(nil, func(notation *utils.ExecOpts, artifact1 *Artifact, vhost *utils.VirtualHost) { // artifact1 signed with new_e2e.crt OldNotation(AuthOption("", ""), AddKeyOption("e2e.key", "new_e2e.crt")). Exec("sign", artifact1.ReferenceWithDigest(), "-v"). MatchKeyWords(SignSuccessfully) // artifact2 signed with e2e.crt artifact2 := GenerateArtifact("e2e-valid-signature", "") // setup multiple trust store vhost.SetOption(AuthOption("", ""), AddTrustPolicyOption("multiple_trust_store_trustpolicy.json"), AddTrustStoreOption("e2e-new", filepath.Join(NotationE2ELocalKeysDir, "new_e2e.crt")), AddTrustStoreOption("e2e", filepath.Join(NotationE2ELocalKeysDir, "e2e.crt")), ) notation.WithDescription("verify artifact1 with trust store ca/e2e-new"). Exec("verify", artifact1.ReferenceWithDigest(), "-v"). MatchKeyWords(VerifySuccessfully) notation.WithDescription("verify artifact2 with trust store ca/e2e"). Exec("verify", artifact2.ReferenceWithDigest(), "-v"). MatchKeyWords(VerifySuccessfully) }) }) It("overlapped trust stores", func() { Skip("overlapped trust stores were not checked") Host(nil, func(notation *utils.ExecOpts, artifact *Artifact, vhost *utils.VirtualHost) { // artifact signed with new_e2e.crt notation.Exec("sign", artifact.ReferenceWithDigest(), "-v"). MatchKeyWords(SignSuccessfully) // setup overlapped trust store vhost.SetOption(AuthOption("", ""), AddTrustPolicyOption("overlapped_trust_store_trustpolicy.json"), AddTrustStoreOption("e2e", filepath.Join(NotationE2ELocalKeysDir, "e2e.crt"))) notation.ExpectFailure().Exec("verify", artifact.ReferenceWithDigest(), "-v"). MatchErrKeyWords(VerifyFailed) }) }) }) notation-1.2.0/test/e2e/suite/trustpolicy/trusted_identity.go000066400000000000000000000157571466375446100245220ustar00rootroot00000000000000// Copyright The Notary Project Authors. // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package trustpolicy import ( "path/filepath" . "github.com/notaryproject/notation/test/e2e/internal/notation" "github.com/notaryproject/notation/test/e2e/internal/utils" . "github.com/notaryproject/notation/test/e2e/suite/common" . "github.com/onsi/ginkgo/v2" ) var _ = Describe("notation trust policy trusted identity test", func() { It("with unset trusted identity", func() { Host(BaseOptions(), func(notation *utils.ExecOpts, _ *Artifact, vhost *utils.VirtualHost) { vhost.SetOption(AddTrustPolicyOption("unset_trusted_identity_trustpolicy.json")) artifact := GenerateArtifact("e2e-valid-signature", "") notation.ExpectFailure().Exec("verify", artifact.ReferenceWithDigest(), "-v"). MatchErrKeyWords(`trust policy statement "e2e" is either missing trust stores or trusted identities, both must be specified`) }) }) It("with valid trusted identity", func() { Host(BaseOptions(), func(notation *utils.ExecOpts, _ *Artifact, vhost *utils.VirtualHost) { vhost.SetOption(AddTrustPolicyOption("valid_trusted_identity_trustpolicy.json")) artifact := GenerateArtifact("e2e-valid-signature", "") notation.Exec("verify", artifact.ReferenceWithDigest(), "-v"). MatchKeyWords(VerifySuccessfully) }) }) It("with invalid trusted identity", func() { Host(BaseOptions(), func(notation *utils.ExecOpts, _ *Artifact, vhost *utils.VirtualHost) { vhost.SetOption(AddTrustPolicyOption("invalid_trusted_identity_trustpolicy.json")) artifact := GenerateArtifact("e2e-valid-signature", "") notation.ExpectFailure().Exec("verify", artifact.ReferenceWithDigest(), "-v"). MatchErrKeyWords("Failure reason: signing certificate from the digital signature does not match the X.509 trusted identities", VerifyFailed) }) }) It("with malformed trusted identity", func() { Host(BaseOptions(), func(notation *utils.ExecOpts, _ *Artifact, vhost *utils.VirtualHost) { vhost.SetOption(AddTrustPolicyOption("malformed_trusted_identity_trustpolicy.json")) artifact := GenerateArtifact("e2e-valid-signature", "") notation.ExpectFailure().Exec("verify", artifact.ReferenceWithDigest(), "-v"). MatchErrKeyWords(`trust policy statement "e2e" has trusted identity "x509.subject CN=e2e,O=Notary,L=Seattle,ST=WA,C=US" missing separator`) }) }) It("with empty trusted identity", func() { Host(BaseOptions(), func(notation *utils.ExecOpts, _ *Artifact, vhost *utils.VirtualHost) { vhost.SetOption(AddTrustPolicyOption("empty_trusted_identity_trustpolicy.json")) artifact := GenerateArtifact("e2e-valid-signature", "") notation.ExpectFailure().Exec("verify", artifact.ReferenceWithDigest(), "-v"). MatchErrKeyWords(`trust policy statement "e2e" has an empty trusted identity`) }) }) It("with multiple trusted identity", func() { Host(nil, func(notation *utils.ExecOpts, artifact1 *Artifact, vhost *utils.VirtualHost) { // artifact1 signed with new_e2e.crt OldNotation(AuthOption("", ""), AddKeyOption("e2e.key", "new_e2e.crt")). Exec("sign", artifact1.ReferenceWithDigest(), "-v"). MatchKeyWords(SignSuccessfully) // artifact2 signed with e2e.crt artifact2 := GenerateArtifact("e2e-valid-signature", "") // setup multiple trusted identity vhost.SetOption(AuthOption("", ""), AddTrustPolicyOption("multiple_trusted_identity_trustpolicy.json"), AddTrustStoreOption("e2e", filepath.Join(NotationE2ELocalKeysDir, "new_e2e.crt")), AddTrustStoreOption("e2e", filepath.Join(NotationE2ELocalKeysDir, "e2e.crt")), ) notation.Exec("verify", artifact1.ReferenceWithDigest(), "-v"). MatchKeyWords(VerifySuccessfully) notation.Exec("verify", artifact2.ReferenceWithDigest(), "-v"). MatchKeyWords(VerifySuccessfully) }) }) It("with overlapped trusted identities", func() { Host(BaseOptions(), func(notation *utils.ExecOpts, _ *Artifact, vhost *utils.VirtualHost) { vhost.SetOption(AddTrustPolicyOption("overlapped_trusted_identity_trustpolicy.json")) artifact := GenerateArtifact("e2e-valid-signature", "") notation.ExpectFailure().Exec("verify", artifact.ReferenceWithDigest(), "-v"). MatchErrKeyWords(`trust policy statement "e2e" has overlapping x509 trustedIdentities`) }) }) It("with wildcard plus other trusted identities", func() { Host(BaseOptions(), func(notation *utils.ExecOpts, _ *Artifact, vhost *utils.VirtualHost) { vhost.SetOption(AddTrustPolicyOption("wildcard_plus_other_trusted_identity_trustpolicy.json")) artifact := GenerateArtifact("e2e-valid-signature", "") notation.ExpectFailure().Exec("verify", artifact.ReferenceWithDigest(), "-v"). MatchErrKeyWords(`trust policy statement "e2e" uses a wildcard trusted identity '*', a wildcard identity cannot be used in conjunction with other values`) }) }) It("with trusted identities missing organization", func() { Host(BaseOptions(), func(notation *utils.ExecOpts, _ *Artifact, vhost *utils.VirtualHost) { vhost.SetOption(AddTrustPolicyOption("missing_organization_trusted_identity_trustpolicy.json")) artifact := GenerateArtifact("e2e-valid-signature", "") notation.ExpectFailure().Exec("verify", artifact.ReferenceWithDigest(), "-v"). MatchErrKeyWords(`distinguished name (DN) " CN=e2e,L=Seattle,ST=WA,C=US" has no mandatory RDN attribute for "O", it must contain 'C', 'ST' or 'S', and 'O' RDN attributes at a minimum`) }) }) It("with trusted identities missing state", func() { Host(BaseOptions(), func(notation *utils.ExecOpts, _ *Artifact, vhost *utils.VirtualHost) { vhost.SetOption(AddTrustPolicyOption("missing_state_trusted_identity_trustpolicy.json")) artifact := GenerateArtifact("e2e-valid-signature", "") notation.ExpectFailure().Exec("verify", artifact.ReferenceWithDigest(), "-v"). MatchErrKeyWords(`distinguished name (DN) " CN=e2e,O=Notary,L=Seattle,C=US" has no mandatory RDN attribute for "ST", it must contain 'C', 'ST' or 'S', and 'O' RDN attributes at a minimum`) }) }) It("with trusted identities missing country", func() { Host(BaseOptions(), func(notation *utils.ExecOpts, _ *Artifact, vhost *utils.VirtualHost) { vhost.SetOption(AddTrustPolicyOption("missing_country_trusted_identity_trustpolicy.json")) artifact := GenerateArtifact("e2e-valid-signature", "") notation.ExpectFailure().Exec("verify", artifact.ReferenceWithDigest(), "-v"). MatchErrKeyWords(`distinguished name (DN) " CN=e2e,O=Notary,L=Seattle,ST=WA" has no mandatory RDN attribute for "C", it must contain 'C', 'ST' or 'S', and 'O' RDN attributes at a minimum`) }) }) }) notation-1.2.0/test/e2e/suite/trustpolicy/trustpolicy_test.go000066400000000000000000000014361466375446100245440ustar00rootroot00000000000000// Copyright The Notary Project Authors. // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package trustpolicy import ( "testing" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" ) func TestTrustPolicy(t *testing.T) { RegisterFailHandler(Fail) RunSpecs(t, "Trust Policy Suite") } notation-1.2.0/test/e2e/suite/trustpolicy/verification_level.go000066400000000000000000000336121466375446100247560ustar00rootroot00000000000000// Copyright The Notary Project Authors. // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package trustpolicy import ( "path/filepath" . "github.com/notaryproject/notation/test/e2e/internal/notation" "github.com/notaryproject/notation/test/e2e/internal/utils" . "github.com/notaryproject/notation/test/e2e/suite/common" . "github.com/onsi/ginkgo/v2" ) var _ = Describe("notation trust policy verification level test", func() { It("strict level with expired signature", func() { Host(BaseOptions(), func(notation *utils.ExecOpts, _ *Artifact, vhost *utils.VirtualHost) { artifact := GenerateArtifact("e2e-expired-signature", "") notation.ExpectFailure().Exec("verify", artifact.ReferenceWithDigest(), "-v"). MatchErrKeyWords("expiry validation failed.", VerifyFailed) }) }) It("strict level with expired authentic timestamp", func() { Host(nil, func(notation *utils.ExecOpts, _ *Artifact, vhost *utils.VirtualHost) { artifact := GenerateArtifact("e2e-with-expired-cert", "") vhost.SetOption(AuthOption("", ""), AddTrustPolicyOption("trustpolicy.json"), AddTrustStoreOption("e2e", filepath.Join(NotationE2EConfigPath, "localkeys", "expired_e2e.crt")), ) notation.ExpectFailure().Exec("verify", artifact.ReferenceWithDigest(), "-v"). MatchErrKeyWords("authenticTimestamp validation failed", VerifyFailed) }) }) It("strict level with invalid authenticity", func() { Host(nil, func(notation *utils.ExecOpts, _ *Artifact, vhost *utils.VirtualHost) { vhost.SetOption(AuthOption("", ""), AddTrustPolicyOption("trustpolicy.json"), AddTrustStoreOption("e2e", filepath.Join(NotationE2ELocalKeysDir, "new_e2e.crt")), ) // the artifact signed with a different cert from the cert in // trust store. artifact := GenerateArtifact("e2e-valid-signature", "") notation.ExpectFailure().Exec("verify", artifact.ReferenceWithDigest(), "-v"). MatchErrKeyWords("authenticity validation failed", VerifyFailed) }) }) It("strict level with invalid integrity", func() { Host(BaseOptions(), func(notation *utils.ExecOpts, _ *Artifact, vhost *utils.VirtualHost) { artifact := GenerateArtifact("e2e-invalid-signature", "") notation.ExpectFailure().Exec("verify", artifact.ReferenceWithDigest(), "-v"). MatchErrKeyWords("integrity validation failed", VerifyFailed) }) }) It("permissive level with expired signature", func() { Host(BaseOptions(), func(notation *utils.ExecOpts, _ *Artifact, vhost *utils.VirtualHost) { vhost.SetOption(AddTrustPolicyOption("permissive_trustpolicy.json")) artifact := GenerateArtifact("e2e-expired-signature", "") notation.Exec("verify", artifact.ReferenceWithDigest(), "-v"). MatchErrKeyWords("expiry was set to \"log\" and failed with error: digital signature has expired"). MatchKeyWords(VerifySuccessfully) }) }) It("permissive level with expired authentic timestamp", func() { Host(nil, func(notation *utils.ExecOpts, _ *Artifact, vhost *utils.VirtualHost) { artifact := GenerateArtifact("e2e-with-expired-cert", "") vhost.SetOption(AuthOption("", ""), AddTrustPolicyOption("permissive_trustpolicy.json"), AddTrustStoreOption("e2e", filepath.Join(NotationE2EConfigPath, "localkeys", "expired_e2e.crt")), ) notation.Exec("verify", artifact.ReferenceWithDigest(), "-v"). MatchErrKeyWords("Warning: authenticTimestamp was set to \"log\"", "after certificate \"O=Internet Widgits Pty Ltd,ST=Some-State,C=AU\" validity period, it was expired at \"Tue, 27 Jun 2023 06:10:00 +0000\""). MatchKeyWords(VerifySuccessfully) }) }) It("permissive level with invalid authenticity", func() { Host(nil, func(notation *utils.ExecOpts, _ *Artifact, vhost *utils.VirtualHost) { vhost.SetOption(AuthOption("", ""), AddTrustPolicyOption("permissive_trustpolicy.json"), AddTrustStoreOption("e2e", filepath.Join(NotationE2ELocalKeysDir, "new_e2e.crt")), ) // the artifact signed with a different cert from the cert in // trust store. artifact := GenerateArtifact("e2e-valid-signature", "") notation.ExpectFailure().Exec("verify", artifact.ReferenceWithDigest(), "-v"). MatchErrKeyWords("authenticity validation failed", VerifyFailed) }) }) It("permissive level with invalid integrity", func() { Host(BaseOptions(), func(notation *utils.ExecOpts, _ *Artifact, vhost *utils.VirtualHost) { vhost.SetOption(AddTrustPolicyOption("permissive_trustpolicy.json")) artifact := GenerateArtifact("e2e-invalid-signature", "") notation.ExpectFailure().Exec("verify", artifact.ReferenceWithDigest(), "-v"). MatchErrKeyWords("integrity validation failed", VerifyFailed) }) }) It("audit level with expired signature", func() { Host(BaseOptions(), func(notation *utils.ExecOpts, _ *Artifact, vhost *utils.VirtualHost) { vhost.SetOption(AddTrustPolicyOption("audit_trustpolicy.json")) artifact := GenerateArtifact("e2e-expired-signature", "") notation.Exec("verify", artifact.ReferenceWithDigest(), "-v"). MatchErrKeyWords("digital signature has expired", "expiry was set to \"log\""). MatchKeyWords(VerifySuccessfully) }) }) It("audit level with expired authentic timestamp", func() { Host(nil, func(notation *utils.ExecOpts, _ *Artifact, vhost *utils.VirtualHost) { artifact := GenerateArtifact("e2e-with-expired-cert", "") vhost.SetOption(AuthOption("", ""), AddTrustPolicyOption("audit_trustpolicy.json"), AddTrustStoreOption("e2e", filepath.Join(NotationE2EConfigPath, "localkeys", "expired_e2e.crt")), ) notation.Exec("verify", artifact.ReferenceWithDigest(), "-v"). MatchErrKeyWords("Warning: authenticTimestamp was set to \"log\"", "after certificate \"O=Internet Widgits Pty Ltd,ST=Some-State,C=AU\" validity period, it was expired at \"Tue, 27 Jun 2023 06:10:00 +0000\""). MatchKeyWords(VerifySuccessfully) }) }) It("audit level with invalid authenticity", func() { Host(nil, func(notation *utils.ExecOpts, _ *Artifact, vhost *utils.VirtualHost) { vhost.SetOption(AuthOption("", ""), AddTrustPolicyOption("audit_trustpolicy.json"), AddTrustStoreOption("e2e", filepath.Join(NotationE2ELocalKeysDir, "new_e2e.crt")), ) // the artifact signed with a different cert from the cert in // trust store. artifact := GenerateArtifact("e2e-valid-signature", "") notation.Exec("verify", artifact.ReferenceWithDigest(), "-v"). MatchErrKeyWords("Warning: authenticity was set to \"log\"", "signature is not produced by a trusted signer"). MatchKeyWords(VerifySuccessfully) }) }) It("audit level with invalid integrity", func() { Host(BaseOptions(), func(notation *utils.ExecOpts, _ *Artifact, vhost *utils.VirtualHost) { vhost.SetOption(AddTrustPolicyOption("audit_trustpolicy.json")) artifact := GenerateArtifact("e2e-invalid-signature", "") notation.ExpectFailure().Exec("verify", artifact.ReferenceWithDigest(), "-v"). MatchErrKeyWords("integrity validation failed", VerifyFailed) }) }) It("skip level with invalid integrity", func() { Host(BaseOptions(), func(notation *utils.ExecOpts, _ *Artifact, vhost *utils.VirtualHost) { vhost.SetOption(AddTrustPolicyOption("skip_trustpolicy.json")) artifact := GenerateArtifact("e2e-invalid-signature", "") notation.Exec("verify", artifact.ReferenceWithDigest(), "-v"). MatchKeyWords("Trust policy is configured to skip signature verification") }) }) It("strict level with Expiry overridden as log level", func() { Host(BaseOptions(), func(notation *utils.ExecOpts, _ *Artifact, vhost *utils.VirtualHost) { vhost.SetOption(AddTrustPolicyOption("override_strict_trustpolicy.json")) artifact := GenerateArtifact("e2e-expired-signature", "") notation.Exec("verify", artifact.ReferenceWithDigest(), "-v"). MatchErrKeyWords("digital signature has expired", "expiry was set to \"log\""). MatchKeyWords(VerifySuccessfully) }) }) It("strict level with Authentic timestamp overridden as log level", func() { Host(nil, func(notation *utils.ExecOpts, _ *Artifact, vhost *utils.VirtualHost) { artifact := GenerateArtifact("e2e-with-expired-cert", "") vhost.SetOption(AuthOption("", ""), AddTrustPolicyOption("override_strict_trustpolicy.json"), AddTrustStoreOption("e2e", filepath.Join(NotationE2EConfigPath, "localkeys", "expired_e2e.crt")), ) notation.Exec("verify", artifact.ReferenceWithDigest(), "-v"). MatchErrKeyWords("Warning: authenticTimestamp was set to \"log\"", "after certificate \"O=Internet Widgits Pty Ltd,ST=Some-State,C=AU\" validity period, it was expired at \"Tue, 27 Jun 2023 06:10:00 +0000\""). MatchKeyWords(VerifySuccessfully) }) }) It("strict level with Authenticity overridden as log level", func() { Host(nil, func(notation *utils.ExecOpts, _ *Artifact, vhost *utils.VirtualHost) { vhost.SetOption(AuthOption("", ""), AddTrustPolicyOption("override_strict_trustpolicy.json"), AddTrustStoreOption("e2e", filepath.Join(NotationE2ELocalKeysDir, "new_e2e.crt")), ) // the artifact signed with a different cert from the cert in // trust store. artifact := GenerateArtifact("e2e-valid-signature", "") notation.Exec("verify", artifact.ReferenceWithDigest(), "-v"). MatchErrKeyWords("Warning: authenticity was set to \"log\"", "signature is not produced by a trusted signer"). MatchKeyWords(VerifySuccessfully) }) }) It("permissive level with Expiry overridden as enforce level", func() { Host(BaseOptions(), func(notation *utils.ExecOpts, _ *Artifact, vhost *utils.VirtualHost) { vhost.SetOption(AddTrustPolicyOption("override_permissive_trustpolicy.json")) artifact := GenerateArtifact("e2e-expired-signature", "") notation.ExpectFailure().Exec("verify", artifact.ReferenceWithDigest(), "-v"). MatchErrKeyWords("expiry validation failed.", VerifyFailed) }) }) It("permissive level with Authentic timestamp overridden as enforce level", func() { Host(nil, func(notation *utils.ExecOpts, _ *Artifact, vhost *utils.VirtualHost) { vhost.SetOption(AddTrustPolicyOption("override_permissive_trustpolicy.json")) artifact := GenerateArtifact("e2e-with-expired-cert", "") vhost.SetOption(AuthOption("", ""), AddTrustPolicyOption("trustpolicy.json"), AddTrustStoreOption("e2e", filepath.Join(NotationE2EConfigPath, "localkeys", "expired_e2e.crt")), ) notation.ExpectFailure().Exec("verify", artifact.ReferenceWithDigest(), "-v"). MatchErrKeyWords("authenticTimestamp validation failed", VerifyFailed) }) }) It("permissive level with Authenticity overridden as log level", func() { Host(nil, func(notation *utils.ExecOpts, _ *Artifact, vhost *utils.VirtualHost) { vhost.SetOption(AuthOption("", ""), AddTrustPolicyOption("override_permissive_trustpolicy.json"), AddTrustStoreOption("e2e", filepath.Join(NotationE2ELocalKeysDir, "new_e2e.crt")), ) artifact := GenerateArtifact("e2e-valid-signature", "") notation.Exec("verify", artifact.ReferenceWithDigest(), "-v"). MatchErrKeyWords("Warning: authenticity was set to \"log\"", "signature is not produced by a trusted signer"). MatchKeyWords(VerifySuccessfully) }) }) It("permissive level with Integrity overridden as log level", func() { Host(nil, func(notation *utils.ExecOpts, _ *Artifact, vhost *utils.VirtualHost) { vhost.SetOption(AuthOption("", ""), AddTrustPolicyOption("override_integrity_for_permissive_trustpolicy.json"), AddTrustStoreOption("e2e", filepath.Join(NotationE2ELocalKeysDir, "new_e2e.crt")), ) artifact := GenerateArtifact("e2e-valid-signature", "") notation.ExpectFailure().Exec("verify", artifact.ReferenceWithDigest(), "-v"). MatchErrKeyWords(`"integrity" verification can not be overridden in custom signature verification`) }) }) It("audit level with Expiry overridden as enforce level", func() { Host(BaseOptions(), func(notation *utils.ExecOpts, _ *Artifact, vhost *utils.VirtualHost) { vhost.SetOption(AddTrustPolicyOption("override_audit_trustpolicy.json")) artifact := GenerateArtifact("e2e-expired-signature", "") notation.ExpectFailure().Exec("verify", artifact.ReferenceWithDigest(), "-v"). MatchErrKeyWords("expiry validation failed.", VerifyFailed) }) }) It("audit level with Authentic timestamp overridden as enforce level", func() { Host(nil, func(notation *utils.ExecOpts, _ *Artifact, vhost *utils.VirtualHost) { vhost.SetOption(AddTrustPolicyOption("override_audit_trustpolicy.json")) artifact := GenerateArtifact("e2e-with-expired-cert", "") vhost.SetOption(AuthOption("", ""), AddTrustPolicyOption("trustpolicy.json"), AddTrustStoreOption("e2e", filepath.Join(NotationE2EConfigPath, "localkeys", "expired_e2e.crt")), ) notation.ExpectFailure().Exec("verify", artifact.ReferenceWithDigest(), "-v"). MatchErrKeyWords("authenticTimestamp validation failed", VerifyFailed) }) }) It("audit level with Authenticity overridden as enforce level", func() { Host(nil, func(notation *utils.ExecOpts, _ *Artifact, vhost *utils.VirtualHost) { vhost.SetOption(AuthOption("", ""), AddTrustPolicyOption("override_audit_trustpolicy.json"), AddTrustStoreOption("e2e", filepath.Join(NotationE2ELocalKeysDir, "new_e2e.crt")), ) // the artifact signed with a different cert from the cert in // trust store. artifact := GenerateArtifact("e2e-valid-signature", "") notation.ExpectFailure().Exec("verify", artifact.ReferenceWithDigest(), "-v"). MatchErrKeyWords("authenticity validation failed", VerifyFailed) }) }) }) notation-1.2.0/test/e2e/testdata/000077500000000000000000000000001466375446100166305ustar00rootroot00000000000000notation-1.2.0/test/e2e/testdata/config/000077500000000000000000000000001466375446100200755ustar00rootroot00000000000000notation-1.2.0/test/e2e/testdata/config/configjsons/000077500000000000000000000000001466375446100224175ustar00rootroot00000000000000notation-1.2.0/test/e2e/testdata/config/configjsons/pass_credential_helper_config.json000066400000000000000000000000351466375446100313340ustar00rootroot00000000000000{ "credsStore": "pass" } notation-1.2.0/test/e2e/testdata/config/localkeys/000077500000000000000000000000001466375446100220635ustar00rootroot00000000000000notation-1.2.0/test/e2e/testdata/config/localkeys/e2e.crt000066400000000000000000000022341466375446100232510ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIIDOjCCAiKgAwIBAgIBUTANBgkqhkiG9w0BAQsFADBLMQswCQYDVQQGEwJVUzEL MAkGA1UECBMCV0ExEDAOBgNVBAcTB1NlYXR0bGUxDzANBgNVBAoTBk5vdGFyeTEM MAoGA1UEAxMDZTJlMCAXDTIyMTIxOTAyNDMzOVoYDzIxMjIxMjE5MDI0MzM5WjBL MQswCQYDVQQGEwJVUzELMAkGA1UECBMCV0ExEDAOBgNVBAcTB1NlYXR0bGUxDzAN BgNVBAoTBk5vdGFyeTEMMAoGA1UEAxMDZTJlMIIBIjANBgkqhkiG9w0BAQEFAAOC AQ8AMIIBCgKCAQEAwNrKIzpESDkY7Kah+sWKeXDNE7yUBaqW58cyMtkpk9/Q6QLb ndMxdt8VjD3y4bSBhZXLdUkW0S4hSNZXQwkYm3yooxJXj+uOI9NnEPt/gAkPYri4 TdKpTFSBzE4uJ0HrYDBUir3Dars/CXiusmdCnSWKsgm05ItkHdHEGtor716aWdnG uFNvyzJyaC3XLFpo1OwEwTyxf4Yix4UtvwNDB4TOJRH84avSoFCua8xbRpiBJ0Qo X6zY0Yr9qbvMBQIcmpQ3uWOiEeVMDjRE0XzhiWSevVfZTPJcYpsAyBTJGJLIcHU7 JMYnwKP6IzUg5T589lvRVD7up4sAC7izOKGzPQIDAQABoycwJTAOBgNVHQ8BAf8E BAMCB4AwEwYDVR0lBAwwCgYIKwYBBQUHAwMwDQYJKoZIhvcNAQELBQADggEBAKac aJ+O9rTIdfLHCdneLQ6RN92Id5dl8PGh/Xig3AWSOtLgIiRRsdxVH5PZkGOHosel lQG5fCvQOwB0x8O+2YDKLOfgVIJWd6X85NdvyCdX2ElYRmvX9ON5WVguGLluwkOf J26M8d8ftXrcc97qaKk4EHS8R/LCWqZNDRiRCA0OtNP9cUkKFaIG1hgWgEieVWnx rCyUDbTX3uCiJKNzSOmI3psF3WIhabU7/7gBm95nWhgwS91qAxavccVkY6hqAPj9 tjJH5UPI5RR8kDB5rWiCIx1YHuH+z1eAYUHWVvZvneVniNBI8qGoGBz9HkrX5ecf +V7zaxyy0FoWlEX1z8k= -----END CERTIFICATE----- notation-1.2.0/test/e2e/testdata/config/localkeys/e2e.key000066400000000000000000000032501466375446100232500ustar00rootroot00000000000000-----BEGIN PRIVATE KEY----- MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQDA2sojOkRIORjs pqH6xYp5cM0TvJQFqpbnxzIy2SmT39DpAtud0zF23xWMPfLhtIGFlct1SRbRLiFI 1ldDCRibfKijEleP644j02cQ+3+ACQ9iuLhN0qlMVIHMTi4nQetgMFSKvcNquz8J eK6yZ0KdJYqyCbTki2Qd0cQa2ivvXppZ2ca4U2/LMnJoLdcsWmjU7ATBPLF/hiLH hS2/A0MHhM4lEfzhq9KgUK5rzFtGmIEnRChfrNjRiv2pu8wFAhyalDe5Y6IR5UwO NETRfOGJZJ69V9lM8lximwDIFMkYkshwdTskxifAo/ojNSDlPnz2W9FUPu6niwAL uLM4obM9AgMBAAECggEAOmFA9Z8K0o4uRF1BBYfNHmwOOJ5KdNiqK+m6AXiJxJDp TdAmqUUoYSKxBC4wmzCoUMcCuzNv68y6GLGB4vIa996psgu4ZAHbmm7BcXugoiKb /LeMW0qdI1UlobI8+HdVCF45CXLeeC3MrFJTGAB4Qtf9f12+27xyhzBb3AEVcbU8 HbYEAX5i8yxHekkm6P6Tbvl0sN2WRd7iGzB32ozVNCBJjXRuP78DrTGwLFUpXgoE 0FGTBAiOiHvSSU2SOEAo0OJYNjgDUatYHGgXUpVLa0Xwl8hTj3+GzxsYGVPOHlD4 ylpGL+O1zwpeDbEsCKwdLzdw3zy1xpCdQLIvnCKuRQKBgQDbwNYU3OtPZcIva3NZ Y3uxovLRZcabvQ8ygO6ZWr64ci4Tso+R/tXNwHyjTfAXHYHnZK+Zecj7gMZocg+1 LBmzzBoevuby25v/D2ShfbtRbQn8LPtsfGo2EWi8WpHOVHUVsfdo5Nf3Mr5Ru5vj Q5t8GpuEZfEJTGJ9A+mmwl1WkwKBgQDgqiYqJ0AroNhSsmTfIS2G5tyFBZHJr3SG niwb9aWYNdtOj7K/EZVW4DKjEooV2IzaiH61JnDYTusGyGq2IpI5Orm5sGCPV/T2 v8KYffuG8RHfLU/aLVx3Uc3YwJ0kkrS1/d/e8e2FxlLPfWCfQFBUfz+EyHMfmcNI hTP3SFag7wKBgBLUw4OmKsPzBGDr5EaUr9TZEdW/0vUljlfVlJyvmghUQH6Pnp30 KH4pMIUN+LUCYk9h4WpVdVOYBWiN3aq4zLsLknFwCnplN0j0GLt9+r9PiLuDX0g8 oR1hAIijejskIaRqS7lBYwBb6NM4MHOZJ4nK/eiqk60oTohH/Y28uiLhAoGALqxK d3FmDGpC5pM3D3/GBIOLhnsMuXig3aKYiUp0F3YA1IZX3QfbHYxAGM6uLzGouXGn 2RxeTyG+A8+5E5OFCyyfDuuMc9sTAfv+gk2R5ovIabPxJeNMlWFCQWhqfQGZV0Gh s6BQ9vynkYF7hDoJNjSlToGSIRuBjVxW2mWF0bMCgYAtdYBkuKp89hIbP1EMJelt Ig4h6hQcywQd5jRkQXMp9RyVZ2ypvR4fA+aM0MQE6mVPa6BEXNzm1myGFN1Ob0LM JvfMJyjKxHcGIcJEx64wtHMoZF8n/6JSt2CkHfSk3jjZlW+U/Mu1A07JbIKya8Dg GLuZnP4KhavycZfHMENsXg== -----END PRIVATE KEY----- notation-1.2.0/test/e2e/testdata/config/localkeys/expired_e2e.crt000066400000000000000000000023611466375446100247720ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIIDeDCCAmCgAwIBAgIUOmmr4z226FR1IU/HHg29TVLTYk8wDQYJKoZIhvcNAQEL BQAwRTEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMRMwEQYDVQQI DApTb21lLVN0YXRlMQswCQYDVQQGEwJBVTAeFw0yMzA2MjYwNjEwMDBaFw0yMzA2 MjcwNjEwMDBaMEUxITAfBgNVBAoMGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDET MBEGA1UECAwKU29tZS1TdGF0ZTELMAkGA1UEBhMCQVUwggEiMA0GCSqGSIb3DQEB AQUAA4IBDwAwggEKAoIBAQDAm29HFRPQOpSgfjdYGmX+/UgE2vD36Y5U9AwEyMGP WAgHdRCCFPe0RkQic15HjwZRp9AkaJN+SGdkcpjaYdWVphBeDInu+9o7CmhaA5z8 yonm7UfQVO2813AZoXOdAyhiuVTN2CqPAhRk94yCONVtWrCsQqgEuPqSl61sB6+4 t2EY20xPFGsGJu/y9D5ihnqBsIAcohVh3mRTcQ3IRi1VvWiPApCD9CN5eyKZO07I RNANeKRn+yX+o0M7pDfL5b6lZhHTxihhfZXSw/9JEDbucBzWLvQ8nvn/VA+CNCQO OTcCfPCYTjTeCvyuskxwJJ1p3KNdHMgP28RsQiIq4ynfAgMBAAGjYDBeMB0GA1Ud DgQWBBR8Nz1AyK8YixMB+6CaZzVIk9NQ4zAfBgNVHSMEGDAWgBR8Nz1AyK8YixMB +6CaZzVIk9NQ4zAMBgNVHRMBAf8EAjAAMA4GA1UdDwEB/wQEAwIHgDANBgkqhkiG 9w0BAQsFAAOCAQEAkM+lfszmhNWVV17A0in4B3Uw3gOSOLbhMYlpRuJQXXwNQV44 m5UE5xFChjB7mzYbRu2+aClTqoOdoz8C1VcYwkSfcr9y/3IyH2d0FJr+QIDWm32G RC7DMnl/cpGeJgw2/qoxyZ5xmodpneGdRGFc93nO1Bxp2oyB7WC3hBZtbufjE+Kr t4uOhsun8/KtrCXX9JBmpD1GmP6rfml1bIguEW0Ga3jeNloDXk/KanHoJxlteNF6 SJBN4BHfFsDzAWyOdzoU98MgEBbsoJradfM7Js5gZe3bXI3jt4vy97q73C9USbUv SCPUyf6klJDTo1IvyCjWE+VRbOrgG46L6PULNQ== -----END CERTIFICATE----- notation-1.2.0/test/e2e/testdata/config/localkeys/new_e2e.crt000066400000000000000000000023611466375446100241230ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIIDejCCAmKgAwIBAgIUKIwC3uXHKr4kiapZR9pLBWI1EowwDQYJKoZIhvcNAQEL BQAwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDAgFw0yMjEyMzAwMzA1MTVaGA8yMTIy MTIwNjAzMDUxNVowRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUx ITAfBgNVBAoMGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDCCASIwDQYJKoZIhvcN AQEBBQADggEPADCCAQoCggEBAMDayiM6REg5GOymofrFinlwzRO8lAWqlufHMjLZ KZPf0OkC253TMXbfFYw98uG0gYWVy3VJFtEuIUjWV0MJGJt8qKMSV4/rjiPTZxD7 f4AJD2K4uE3SqUxUgcxOLidB62AwVIq9w2q7Pwl4rrJnQp0lirIJtOSLZB3RxBra K+9emlnZxrhTb8sycmgt1yxaaNTsBME8sX+GIseFLb8DQweEziUR/OGr0qBQrmvM W0aYgSdEKF+s2NGK/am7zAUCHJqUN7ljohHlTA40RNF84Ylknr1X2UzyXGKbAMgU yRiSyHB1OyTGJ8Cj+iM1IOU+fPZb0VQ+7qeLAAu4szihsz0CAwEAAaNgMF4wHQYD VR0OBBYEFM9EV80S9zkWVo+f6yMitzZQQ5qgMB8GA1UdIwQYMBaAFM9EV80S9zkW Vo+f6yMitzZQQ5qgMAwGA1UdEwEB/wQCMAAwDgYDVR0PAQH/BAQDAgeAMA0GCSqG SIb3DQEBCwUAA4IBAQCuR/mPwOjMaUVOxhOEtzsDlOwHypemEh9ZqGTCl/HPoBsp RLDRRKp97ioXlkzoxmNgSlh1CYuDi5T2fGv09FoZtG0gTBbTcFyZBoeymcBWLMRI 4MKhUlQq6lQsGa+fuPkE+bhGW7caPrLQfA1N/RePezhH8U7ee76KS5cbtshf2bkz iqA2S/eWPXRC/9bA2feKL/mN/mKZ3YamCXSHCtRqUdGR0mQPY7XWXwrqabol0NoV jC2EjO8yah+xjxe4ZnC5A2ON/9ZgKQqr2u0KrwkG3qXO+bTS93ZdPUEVhVSk7ARp NLma67dY8qSB3bivqlQDV59zBLDAU///xxd67tP5 -----END CERTIFICATE----- notation-1.2.0/test/e2e/testdata/config/timestamp/000077500000000000000000000000001466375446100221005ustar00rootroot00000000000000notation-1.2.0/test/e2e/testdata/config/timestamp/CertChain.pem000066400000000000000000000023221466375446100244420ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIIBljCCATugAwIBAgIQJOyDt70f+HOyQMEt06yvpjAKBggqhkjOPQQDAjAkMRAw DgYDVQQKEwdBY21lIENvMRAwDgYDVQQDEwdSb290IENBMB4XDTIyMDcyMjA1MjEz N1oXDTIzMDcyMjA1MjEzN1owKDEQMA4GA1UEChMHQWNtZSBDbzEUMBIGA1UEAwwL dGVzdF9jZXJ0XzEwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAATnf3lSRtUYOeph UQZvUm5niB8kpm7kn6iAm2zwCTBeqKbUtgESCbN+x6TTpWZIaEo+CDu1rPUdicB3 FUwXNzz8o0swSTAOBgNVHQ8BAf8EBAMCBaAwEwYDVR0lBAwwCgYIKwYBBQUHAwEw DAYDVR0TAQH/BAIwADAUBgNVHREEDTALgglsb2NhbGhvc3QwCgYIKoZIzj0EAwID SQAwRgIhAMgdV/zJnwK0J4ZBXZVwAB6abpgNcESFScDeQQyIzRs8AiEAjjLTfkXp CuoXnu5/hYy6Li7Smw3UbW3XKkekOELMFYo= -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIIBnjCCAUOgAwIBAgIQBUvhbMcjM35qmJzncyZ5tzAKBggqhkjOPQQDAjAkMRAw DgYDVQQKEwdBY21lIENvMRAwDgYDVQQDEwdSb290IENBMB4XDTIyMDcyMjA1MjEz N1oXDTIzMDcyMjA1MjEzN1owJDEQMA4GA1UEChMHQWNtZSBDbzEQMA4GA1UEAxMH Um9vdCBDQTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABGmby5GUiBDR+Ge+s/R9 EqOfoDwEdDBPYU0emJg8j8CPJGM0ldalI1Sk7YMTIi34clvfTqEixE7nDwQj8FjQ VvCjVzBVMA4GA1UdDwEB/wQEAwICBDATBgNVHSUEDDAKBggrBgEFBQcDATAPBgNV HRMBAf8EBTADAQH/MB0GA1UdDgQWBBTcOHZMx0z3I9Hi8oa2Kp0umdXOsTAKBggq hkjOPQQDAgNJADBGAiEArHTaO3f6vaiI+4IOrR7SYSzeHIAqoFAWFcf1yOzxDA4C IQDRcDIPWJd7pXvFJT/Q++Vkq9QuUhqrigCQDkgksnxf5w== -----END CERTIFICATE----- notation-1.2.0/test/e2e/testdata/config/timestamp/DigiCertTSARootSHA384.cer000066400000000000000000000026241466375446100263000ustar00rootroot0000000000000000xW!29wu\0  *H  0b1 0 UUS10U  DigiCert Inc10U www.digicert.com1!0UDigiCert Trusted Root G40 130801120000Z 380115120000Z0b1 0 UUS10U  DigiCert Inc10U www.digicert.com1!0UDigiCert Trusted Root G40"0  *H 0 sh޻]J<0"0i3§%.!=Y)=Xvͮ{ 08VƗmy_pUA2s*n|!LԼu]xf:1D3@ZI橠gݤ'O9X$\Fdivv=Y]BvizHftKc:=E%D+~am3K}Ï!Ռp,A`cDvb~d3щίCw !T)%lRQGt&Auz_?ɼA[P1r" |Lu?c!_ QkoOE_ ~ &i/-٩B0@0U00U0Uq]dL.g?纘O0  *H  a}lđádhVwpJx\ _)V 6I]Dcଡ଼f# =ymkTY9"SD]Pz}b! sfѠ`_襴m5|Z֢8xM Gr 20Y.qVjoPmhz6z$ Pz#aB)͢ Aќd&LPAq=?Mp# J܁2  Ok t094!U2qI(PMMuACDO,6E#SlogUFL?n(Zy&ҤbJGJ gf~[A;;cTQ*xίI󒙶a҅POBl C:qM&5]b2Ҡ+TWJ'S趉m[h#QV𦀠Su)wތ!G=uf~notation-1.2.0/test/e2e/testdata/config/timestamp/Empty.txt000066400000000000000000000000001466375446100237250ustar00rootroot00000000000000notation-1.2.0/test/e2e/testdata/config/timestamp/globalsignTSARoot.cer000066400000000000000000000026071466375446100261350ustar00rootroot0000000000000000kE3ÅeHEQ0  *H  0L1 0U GlobalSign Root CA - R610U  GlobalSign10U GlobalSign0 141210000000Z 341210000000Z0L1 0U GlobalSign Root CA - R610U  GlobalSign10U GlobalSign0"0  *H 0 sf{< E ,H[!C7y2,LC)0Ӭ!3vT"*M .phS\ӝD DfFm%]1QTFMۙ\xy]>LUop0BF“}c{?q|蘮x4% k;AHzswiǶ\X(+l^m{%7hB]Pu6i{ni HY{`zdiCLlS-^r>TȽgLEӹ0#LٙZW̻S,Ğ4L$x_|Jt%>K,V8\yt-a ~~OY_-CRF fGywT[$7EZNDHذ IIeqnE>l gZɤnotation-1.2.0/test/e2e/testdata/config/timestamp/intermediate.pem000066400000000000000000000020041466375446100252510ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIICyjCCAbKgAwIBAgIBATANBgkqhkiG9w0BAQsFADAPMQ0wCwYDVQQDDARSb290 MCAXDTIyMDYzMDE5MjAwM1oYDzMwMjExMDMxMTkyMDAzWjAYMRYwFAYDVQQDDA1J bnRlcm1lZGlhdGUxMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA1JTs aiC/7+bho43kMVyHDwCsuocYp4PvYahB59NsKDR4QbrImU5ziaQ94D0DQqthe9pm qOW0SxN/vSRJAZFELxacrB9hc1y4MjiDYaRSt/LVx7astylBV/QRpmxWSEqp0Avu 6nMJivIa1sD0WIEchizx6jG9BI5ULr9LbJICYvMgDalQR+0JGG+rKWnf1mPZyxEu 9zEh215LCg5K56P3W5kC8fKBXSdSgTqZAvHzp6u78qet9S8gARtOEfS03A/7y7MC U0Sn2wdQyQdci0PBsR2sTZvUw179Cr93r5aRbb3I6jXgMWHAP2vvIndb9CM9ePyY yEy4Je7oWVVfMQ3CWQIDAQABoyYwJDASBgNVHRMBAf8ECDAGAQH/AgEBMA4GA1Ud DwEB/wQEAwICBDANBgkqhkiG9w0BAQsFAAOCAQEALR0apUQVbWGmagLUz4Y/bRsl mY9EJJXCiLuSxVWd3offjZfQTlGkQkCAW9FOQnm7JhEtaaHF1+AEVLo56/Gsd/hk sXsrBagYGi72jun7QTb6j7iZ3X9zanrP3SjdkpjVnqxRfH83diSh0r68Xruq1NSK qhUy1V+KQaXF0SSEutPqdTCoXUyxyXohVLU78uqZX/jx9Nc1XDuW9AZd+hMsLdk8 qGJqHYFvj2vOHGMTeYk8dWgMBthQeL0wdsg2AvKtAvn6FQXCN7mKCWjpFTtYsU8v NsesS9M/i+geJjR/8/DDT3RP7S100BtCMm4XfHfmKcjXVaBh5evQVqGsa6TKLw== -----END CERTIFICATE-----notation-1.2.0/test/e2e/testdata/config/timestamp/invalid.crt000066400000000000000000000000661466375446100242420ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- -----END CERTIFICATE----- notation-1.2.0/test/e2e/testdata/config/timestamp/notSelfIssued.crt000066400000000000000000000023251466375446100254030ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIIDZDCCAkygAwIBAgIBATANBgkqhkiG9w0BAQsFADBDMQswCQYDVQQGEwJVUzEJ MAcGA1UECBMAMQkwBwYDVQQHEwAxDzANBgNVBAoTBk5vdGFyeTENMAsGA1UEAxME dGVzdDAeFw0yNDA3MjIwNTIwMzZaFw0yNDA4MjIwNTIwMzZaMEwxCzAJBgNVBAYT AlVTMQswCQYDVQQIEwJXQTEQMA4GA1UEBxMHU2VhdHRsZTEPMA0GA1UEChMGTm90 YXJ5MQ0wCwYDVQQDEwR0ZXN0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC AQEAp7XmYGf3p4JS/y5v6AIc/TxQvPTwIRxVmctNmDm5kG3LDUoGAAJbicTUfI/0 Un38j/PlHNKQz9hKPwV+oYotKuQrVhaA2fft+INl36tvgCAPr8yX3ToOMCLr/UlT zQ7o9TB7IpnVT9DR9uik9MWfkz0Db5ARG1POquvSy2QM5wseEA58313YJ/7Em/Cq FCH5s9THCfQKpb09MZ/RTEggNqU4zGADah8e1KieYeZntM/hrw7sW5oeUueKG4D4 3kvL8o7n1k6C+w8LwaOGhYXCQ51JxTE3lnmTrDdFRuKGObpNFNbLxdJPVLuHT1Nu bwVxj5APBJQyEyja3jJ9qLQANwIDAQABo1owWDAOBgNVHQ8BAf8EBAMCAgQwEwYD VR0lBAwwCgYIKwYBBQUHAwMwEgYDVR0TAQH/BAgwBgEB/wIBATAdBgNVHQ4EFgQU yPScYtI4hs0+ibHaRV9BsgY734AwDQYJKoZIhvcNAQELBQADggEBABJHh3NELq1b jcJiJX76DwDTx+FGGN96/+T5622FGg1kHeAwuxS6pQODJNrVofbrhGAqaXTDT/Tz 0b0AA5XCohmBFZQRwMh+C5QkFiIcZ9VMMBc6KTQT8DEgjI6Qo/OW2TDGOoFuAhmh 4a1ACHszuHS55Th+0TKLqeZNA6DnL9IBm0RX1FJXbqhjX52ZnRH3Zqe7uML+kxKt LUdfnxHrpA1G2ugyAj+K7K6vth5QpezwCS1PZD2s5vlJd6clawxm5qRyyU46ow/y 7bpTSEyg6PIWWh/qv4O2t4NMa1OoRkIXx/ppsKH9XwbRg/WZ0VWlGVS6GxBtLSpG tkyaxSRLdz0= -----END CERTIFICATE----- notation-1.2.0/test/e2e/testdata/config/trustpolicies/000077500000000000000000000000001466375446100230065ustar00rootroot00000000000000notation-1.2.0/test/e2e/testdata/config/trustpolicies/any_registry_scope_trust_policy.json000066400000000000000000000005241466375446100324320ustar00rootroot00000000000000{ "version": "1.0", "trustPolicies": [ { "name": "e2e", "registryScopes": [ "*" ], "signatureVerification": { "level" : "strict" }, "trustStores": [ "ca:e2e" ], "trustedIdentities": [ "*" ] } ] }notation-1.2.0/test/e2e/testdata/config/trustpolicies/audit_trustpolicy.json000066400000000000000000000005231466375446100274700ustar00rootroot00000000000000{ "version": "1.0", "trustPolicies": [ { "name": "e2e", "registryScopes": [ "*" ], "signatureVerification": { "level" : "audit" }, "trustStores": [ "ca:e2e" ], "trustedIdentities": [ "*" ] } ] }notation-1.2.0/test/e2e/testdata/config/trustpolicies/empty_registry_scope_trustpolicy.json000066400000000000000000000005171466375446100326440ustar00rootroot00000000000000{ "version": "1.0", "trustPolicies": [ { "name": "e2e", "registryScopes": [], "signatureVerification": { "level" : "strict" }, "trustStores": [ "ca:e2e" ], "trustedIdentities": [ "*" ] } ] }notation-1.2.0/test/e2e/testdata/config/trustpolicies/empty_trusted_identity_trustpolicy.json000066400000000000000000000004651466375446100332100ustar00rootroot00000000000000{ "version": "1.0", "trustPolicies": [ { "name": "e2e", "registryScopes": [ "*" ], "signatureVerification": { "level" : "strict" }, "trustStores": [ "ca:e2e" ], "trustedIdentities": [""] } ] }notation-1.2.0/test/e2e/testdata/config/trustpolicies/generate_test_trustpolicy.json000066400000000000000000000005261466375446100312160ustar00rootroot00000000000000{ "version": "1.0", "trustPolicies": [ { "name": "sKey", "registryScopes": [ "*" ], "signatureVerification": { "level" : "strict" }, "trustStores": [ "ca:sKey" ], "trustedIdentities": [ "*" ] } ] }notation-1.2.0/test/e2e/testdata/config/trustpolicies/invalid_format_trustpolicy.json000066400000000000000000000001511466375446100313550ustar00rootroot00000000000000{ "version": "1.0", "trustPolicies": [ { "name": "e2e", "registryScopes": [ "*" ] }} ] }notation-1.2.0/test/e2e/testdata/config/trustpolicies/invalid_registry_scope_trustpolicy.json000066400000000000000000000005621466375446100331340ustar00rootroot00000000000000{ "version": "1.0", "trustPolicies": [ { "name": "e2e", "registryScopes": [ "localhost:5000/invalid-test-repo"], "signatureVerification": { "level" : "strict" }, "trustStores": [ "ca:e2e" ], "trustedIdentities": [ "*" ] } ] }notation-1.2.0/test/e2e/testdata/config/trustpolicies/invalid_trust_store_trustpolicy.json000066400000000000000000000005361466375446100324710ustar00rootroot00000000000000{ "version": "1.0", "trustPolicies": [ { "name": "e2e", "registryScopes": [ "*" ], "signatureVerification": { "level" : "strict" }, "trustStores": [ "ca:invalid_store" ], "trustedIdentities": [ "*" ] } ] }notation-1.2.0/test/e2e/testdata/config/trustpolicies/invalid_trusted_identity_trustpolicy.json000066400000000000000000000006061466375446100334750ustar00rootroot00000000000000{ "version": "1.0", "trustPolicies": [ { "name": "e2e", "registryScopes": [ "*" ], "signatureVerification": { "level" : "strict" }, "trustStores": [ "ca:e2e" ], "trustedIdentities": [ "x509.subject: CN=e2e,O=Notary,L=Seattle,ST=WA,C=USB" ] } ] }notation-1.2.0/test/e2e/testdata/config/trustpolicies/malformed_registry_scope_trustpolicy.json000066400000000000000000000005531466375446100334540ustar00rootroot00000000000000{ "version": "1.0", "trustPolicies": [ { "name": "e2e", "registryScopes": [ "localhost:5000\\test-repo"], "signatureVerification": { "level" : "strict" }, "trustStores": [ "ca:e2e" ], "trustedIdentities": [ "*" ] } ] }notation-1.2.0/test/e2e/testdata/config/trustpolicies/malformed_trust_store_trustpolicy.json000066400000000000000000000005211466375446100330030ustar00rootroot00000000000000{ "version": "1.0", "trustPolicies": [ { "name": "e2e", "registryScopes": [ "*" ], "signatureVerification": { "level" : "strict" }, "trustStores": [ "ca:" ], "trustedIdentities": [ "*" ] } ] }notation-1.2.0/test/e2e/testdata/config/trustpolicies/malformed_trusted_identity_trustpolicy.json000066400000000000000000000006041466375446100340130ustar00rootroot00000000000000{ "version": "1.0", "trustPolicies": [ { "name": "e2e", "registryScopes": [ "*" ], "signatureVerification": { "level" : "strict" }, "trustStores": [ "ca:e2e" ], "trustedIdentities": [ "x509.subject CN=e2e,O=Notary,L=Seattle,ST=WA,C=US" ] } ] }missing_country_trusted_identity_trustpolicy.json000066400000000000000000000006001466375446100352160ustar00rootroot00000000000000notation-1.2.0/test/e2e/testdata/config/trustpolicies{ "version": "1.0", "trustPolicies": [ { "name": "e2e", "registryScopes": [ "*" ], "signatureVerification": { "level" : "strict" }, "trustStores": [ "ca:e2e" ], "trustedIdentities": [ "x509.subject: CN=e2e,O=Notary,L=Seattle,ST=WA" ] } ] }missing_organization_trusted_identity_trustpolicy.json000066400000000000000000000005741466375446100362310ustar00rootroot00000000000000notation-1.2.0/test/e2e/testdata/config/trustpolicies{ "version": "1.0", "trustPolicies": [ { "name": "e2e", "registryScopes": [ "*" ], "signatureVerification": { "level" : "strict" }, "trustStores": [ "ca:e2e" ], "trustedIdentities": [ "x509.subject: CN=e2e,L=Seattle,ST=WA,C=US" ] } ] }missing_state_trusted_identity_trustpolicy.json000066400000000000000000000005771466375446100346500ustar00rootroot00000000000000notation-1.2.0/test/e2e/testdata/config/trustpolicies{ "version": "1.0", "trustPolicies": [ { "name": "e2e", "registryScopes": [ "*" ], "signatureVerification": { "level" : "strict" }, "trustStores": [ "ca:e2e" ], "trustedIdentities": [ "x509.subject: CN=e2e,O=Notary,L=Seattle,C=US" ] } ] }multi_statements_with_multi_wildcard_registry_scope_trustpolicy.json000066400000000000000000000011631466375446100411440ustar00rootroot00000000000000notation-1.2.0/test/e2e/testdata/config/trustpolicies{ "version": "1.0", "trustPolicies": [ { "name": "e2e", "registryScopes": [ "*"], "signatureVerification": { "level" : "strict" }, "trustStores": [ "ca:e2e" ], "trustedIdentities": [ "*" ] }, { "name": "e2e2", "registryScopes": [ "*"], "signatureVerification": { "level" : "strict" }, "trustStores": [ "ca:e2e" ], "trustedIdentities": [ "*" ] } ] }multi_statements_with_the_same_name_trustpolicy.json000066400000000000000000000012441466375446100356050ustar00rootroot00000000000000notation-1.2.0/test/e2e/testdata/config/trustpolicies{ "version": "1.0", "trustPolicies": [ { "name": "e2e", "registryScopes": [ "localhost:5000/test-repo10"], "signatureVerification": { "level" : "strict" }, "trustStores": [ "ca:e2e" ], "trustedIdentities": [ "*" ] }, { "name": "e2e", "registryScopes": [ "localhost:5000/test-repo11"], "signatureVerification": { "level" : "strict" }, "trustStores": [ "ca:e2e" ], "trustedIdentities": [ "*" ] } ] }multi_statements_with_the_same_registry_scope_trustpolicy.json000066400000000000000000000012431466375446100377250ustar00rootroot00000000000000notation-1.2.0/test/e2e/testdata/config/trustpolicies{ "version": "1.0", "trustPolicies": [ { "name": "e2e", "registryScopes": [ "localhost:5000/test-repo8"], "signatureVerification": { "level" : "strict" }, "trustStores": [ "ca:e2e" ], "trustedIdentities": [ "*" ] }, { "name": "e2e2", "registryScopes": [ "localhost:5000/test-repo8"], "signatureVerification": { "level" : "strict" }, "trustStores": [ "ca:e2e" ], "trustedIdentities": [ "*" ] } ] }multi_statements_with_wildcard_registry_scope_trustpolicy.json000066400000000000000000000012131466375446100377260ustar00rootroot00000000000000notation-1.2.0/test/e2e/testdata/config/trustpolicies{ "version": "1.0", "trustPolicies": [ { "name": "e2e", "registryScopes": [ "localhost:5000/test-repo9"], "signatureVerification": { "level" : "strict" }, "trustStores": [ "ca:e2e" ], "trustedIdentities": [ "*" ] }, { "name": "e2e2", "registryScopes": [ "*"], "signatureVerification": { "level" : "strict" }, "trustStores": [ "ca:e2e" ], "trustedIdentities": [ "*" ] } ] }notation-1.2.0/test/e2e/testdata/config/trustpolicies/multiple_registry_scope_trustpolicy.json000066400000000000000000000006111466375446100333340ustar00rootroot00000000000000{ "version": "1.0", "trustPolicies": [ { "name": "e2e", "registryScopes": [ "localhost:5000/test-repo3", "localhost:5000/test-repo2" ], "signatureVerification": { "level" : "strict" }, "trustStores": [ "ca:e2e" ], "trustedIdentities": [ "*" ] } ] }notation-1.2.0/test/e2e/testdata/config/trustpolicies/multiple_trust_store_trustpolicy.json000066400000000000000000000005421466375446100326730ustar00rootroot00000000000000{ "version": "1.0", "trustPolicies": [ { "name": "e2e", "registryScopes": [ "*" ], "signatureVerification": { "level" : "strict" }, "trustStores": [ "ca:e2e", "ca:e2e-new" ], "trustedIdentities": [ "*" ] } ] }notation-1.2.0/test/e2e/testdata/config/trustpolicies/multiple_trusted_identity_trustpolicy.json000066400000000000000000000007241466375446100337030ustar00rootroot00000000000000{ "version": "1.0", "trustPolicies": [ { "name": "e2e", "registryScopes": [ "*" ], "signatureVerification": { "level" : "strict" }, "trustStores": [ "ca:e2e" ], "trustedIdentities": [ "x509.subject: CN=e2e,O=Notary,L=Seattle,ST=WA,C=US", "x509.subject: O=Internet Widgits Pty Ltd,ST=Some-State,C=AU" ] } ] }notation-1.2.0/test/e2e/testdata/config/trustpolicies/overlapped_registry_scope_trustpolicy.json000066400000000000000000000006071466375446100336470ustar00rootroot00000000000000{ "version": "1.0", "trustPolicies": [ { "name": "e2e", "registryScopes": ["localhost:5000/test-repo6", "localhost:5000/test-repo6"], "signatureVerification": { "level" : "strict" }, "trustStores": [ "ca:e2e" ], "trustedIdentities": [ "*" ] } ] }notation-1.2.0/test/e2e/testdata/config/trustpolicies/overlapped_trust_store_trustpolicy.json000066400000000000000000000005361466375446100332040ustar00rootroot00000000000000{ "version": "1.0", "trustPolicies": [ { "name": "e2e", "registryScopes": [ "*" ], "signatureVerification": { "level" : "strict" }, "trustStores": [ "ca:e2e", "ca:e2e" ], "trustedIdentities": [ "*" ] } ] }notation-1.2.0/test/e2e/testdata/config/trustpolicies/overlapped_trusted_identity_trustpolicy.json000066400000000000000000000007011466375446100342040ustar00rootroot00000000000000{ "version": "1.0", "trustPolicies": [ { "name": "e2e", "registryScopes": [ "*" ], "signatureVerification": { "level" : "strict" }, "trustStores": [ "ca:e2e" ], "trustedIdentities": [ "x509.subject: CN=e2e,O=Notary,L=Seattle,ST=WA,C=US", "x509.subject: CN=e2e,O=Notary,ST=WA,C=US" ] } ] }notation-1.2.0/test/e2e/testdata/config/trustpolicies/override_audit_trustpolicy.json000066400000000000000000000010751466375446100313720ustar00rootroot00000000000000{ "version": "1.0", "trustPolicies": [ { "name": "e2e", "registryScopes": [ "*" ], "signatureVerification": { "level" : "audit", "override" : { "revocation":"enforce", "expiry" : "enforce", "authenticTimestamp": "enforce", "authenticity": "enforce" } }, "trustStores": [ "ca:e2e" ], "trustedIdentities": [ "*" ] } ] }override_integrity_for_permissive_trustpolicy.json000066400000000000000000000012401466375446100353310ustar00rootroot00000000000000notation-1.2.0/test/e2e/testdata/config/trustpolicies{ "version": "1.0", "trustPolicies": [ { "name": "e2e", "registryScopes": [ "*" ], "signatureVerification": { "level": "permissive", "override": { "integrity": "enforce", "revocation": "enforce", "expiry": "enforce", "authenticTimestamp": "enforce", "authenticity": "log" } }, "trustStores": [ "ca:e2e" ], "trustedIdentities": [ "*" ] } ] }notation-1.2.0/test/e2e/testdata/config/trustpolicies/override_permissive_trustpolicy.json000066400000000000000000000010761466375446100324530ustar00rootroot00000000000000{ "version": "1.0", "trustPolicies": [ { "name": "e2e", "registryScopes": [ "*" ], "signatureVerification": { "level" : "permissive", "override" : { "revocation":"enforce", "expiry" : "enforce", "authenticTimestamp": "enforce", "authenticity": "log" } }, "trustStores": [ "ca:e2e" ], "trustedIdentities": [ "*" ] } ] }notation-1.2.0/test/e2e/testdata/config/trustpolicies/override_strict_trustpolicy.json000066400000000000000000000010561466375446100315730ustar00rootroot00000000000000{ "version": "1.0", "trustPolicies": [ { "name": "e2e", "registryScopes": [ "*" ], "signatureVerification": { "level" : "strict", "override" : { "revocation":"log", "expiry" : "log", "authenticTimestamp": "log", "authenticity": "log" } }, "trustStores": [ "ca:e2e" ], "trustedIdentities": [ "*" ] } ] }notation-1.2.0/test/e2e/testdata/config/trustpolicies/permissive_trustpolicy.json000066400000000000000000000005301466375446100305460ustar00rootroot00000000000000{ "version": "1.0", "trustPolicies": [ { "name": "e2e", "registryScopes": [ "*" ], "signatureVerification": { "level" : "permissive" }, "trustStores": [ "ca:e2e" ], "trustedIdentities": [ "*" ] } ] }notation-1.2.0/test/e2e/testdata/config/trustpolicies/quickstart_trustpolicy.json000066400000000000000000000005661466375446100305630ustar00rootroot00000000000000{ "version": "1.0", "trustPolicies": [ { "name": "wabbit-networks-images", "registryScopes": [ "*" ], "signatureVerification": { "level" : "strict" }, "trustStores": [ "ca:wabbit-networks.io" ], "trustedIdentities": [ "*" ] } ] }notation-1.2.0/test/e2e/testdata/config/trustpolicies/registry_scope_trustpolicy.json000066400000000000000000000005521466375446100314250ustar00rootroot00000000000000{ "version": "1.0", "trustPolicies": [ { "name": "e2e", "registryScopes": [ "localhost:5000/test-repo"], "signatureVerification": { "level" : "strict" }, "trustStores": [ "ca:e2e" ], "trustedIdentities": [ "*" ] } ] }notation-1.2.0/test/e2e/testdata/config/trustpolicies/skip_trustpolicy.json000066400000000000000000000003431466375446100273300ustar00rootroot00000000000000{ "version": "1.0", "trustPolicies": [ { "name": "e2e", "registryScopes": [ "*" ], "signatureVerification": { "level" : "skip" } } ] }notation-1.2.0/test/e2e/testdata/config/trustpolicies/timestamp_after_cert_expiry_trustpolicy.json000066400000000000000000000006241466375446100341650ustar00rootroot00000000000000{ "version": "1.0", "trustPolicies": [ { "name": "e2e", "registryScopes": [ "*" ], "signatureVerification": { "level" : "strict", "verifyTimestamp": "afterCertExpiry" }, "trustStores": [ "ca:e2e", "tsa:e2e" ], "trustedIdentities": [ "*" ] } ] }notation-1.2.0/test/e2e/testdata/config/trustpolicies/timestamp_trustpolicy.json000066400000000000000000000006131466375446100303650ustar00rootroot00000000000000{ "version": "1.0", "trustPolicies": [ { "name": "e2e", "registryScopes": [ "*" ], "signatureVerification": { "level" : "strict", "verifyTimestamp": "always" }, "trustStores": [ "ca:e2e", "tsa:e2e" ], "trustedIdentities": [ "*" ] } ] }notation-1.2.0/test/e2e/testdata/config/trustpolicies/trustpolicy.json000066400000000000000000000005241466375446100263030ustar00rootroot00000000000000{ "version": "1.0", "trustPolicies": [ { "name": "e2e", "registryScopes": [ "*" ], "signatureVerification": { "level" : "strict" }, "trustStores": [ "ca:e2e" ], "trustedIdentities": [ "*" ] } ] }notation-1.2.0/test/e2e/testdata/config/trustpolicies/unset_trust_store_trustpolicy.json000066400000000000000000000005121466375446100321730ustar00rootroot00000000000000{ "version": "1.0", "trustPolicies": [ { "name": "e2e", "registryScopes": [ "*" ], "signatureVerification": { "level" : "strict" }, "trustStores": [], "trustedIdentities": [ "*" ] } ] }notation-1.2.0/test/e2e/testdata/config/trustpolicies/unset_trusted_identity_trustpolicy.json000066400000000000000000000004631466375446100332060ustar00rootroot00000000000000{ "version": "1.0", "trustPolicies": [ { "name": "e2e", "registryScopes": [ "*" ], "signatureVerification": { "level" : "strict" }, "trustStores": [ "ca:e2e" ], "trustedIdentities": [] } ] }notation-1.2.0/test/e2e/testdata/config/trustpolicies/valid_trusted_identity_trustpolicy.json000066400000000000000000000006051466375446100331450ustar00rootroot00000000000000{ "version": "1.0", "trustPolicies": [ { "name": "e2e", "registryScopes": [ "*" ], "signatureVerification": { "level" : "strict" }, "trustStores": [ "ca:e2e" ], "trustedIdentities": [ "x509.subject: CN=e2e,O=Notary,L=Seattle,ST=WA,C=US" ] } ] }wildcard_plus_other_registry_scope_trustpolicy.json000066400000000000000000000005571466375446100354700ustar00rootroot00000000000000notation-1.2.0/test/e2e/testdata/config/trustpolicies{ "version": "1.0", "trustPolicies": [ { "name": "e2e", "registryScopes": ["*", "localhost:5000/test-repo7"], "signatureVerification": { "level" : "strict" }, "trustStores": [ "ca:e2e" ], "trustedIdentities": [ "*" ] } ] }wildcard_plus_other_trusted_identity_trustpolicy.json000066400000000000000000000006321466375446100360240ustar00rootroot00000000000000notation-1.2.0/test/e2e/testdata/config/trustpolicies{ "version": "1.0", "trustPolicies": [ { "name": "e2e", "registryScopes": [ "*" ], "signatureVerification": { "level" : "strict" }, "trustStores": [ "ca:e2e" ], "trustedIdentities": [ "*", "x509.subject: CN=e2e,O=Notary,L=Seattle,ST=WA,C=US" ] } ] }notation-1.2.0/test/e2e/testdata/config/trustpolicies/wildcard_trust_store_trustpolicy.json000066400000000000000000000005151466375446100326310ustar00rootroot00000000000000{ "version": "1.0", "trustPolicies": [ { "name": "e2e", "registryScopes": [ "*" ], "signatureVerification": { "level" : "strict" }, "trustStores": ["*"], "trustedIdentities": [ "*" ] } ] }notation-1.2.0/test/e2e/testdata/malicious-plugin/000077500000000000000000000000001466375446100221115ustar00rootroot00000000000000notation-1.2.0/test/e2e/testdata/malicious-plugin/large_file_tarGz.tar.gz000066400000000000000000010050151466375446100265020ustar00rootroot000000000000001 @=JNI#Ab A< A{SL1Ќ]CnSX@U9?;e|fKH̹)We E\b̧ejƢf~hOݷ_?{p *k#@6 {p *k#@6 {p *k#@6 {p *k#@6 {p *k#@6 {p *k#@6 {p *k#@6 {p *k#@ fDK@(@notation-1.2.0/test/e2e/testdata/malicious-plugin/large_file_zip.zip000066400000000000000000010056401466375446100256160ustar00rootroot00000000000000PK["X" @large_file.txt  ك FPUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUa_AUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU=8mUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU@TUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU؃ FPUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUa_AUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU=8mUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU@TUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU؃ FPUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUa_AUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU=8mUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU@TUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU؃ FPUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUa_AUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU=8mUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU@TUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU؃ FPUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUa_AUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU=8mUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU@TUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU؃ FPUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUa_AUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU=8mUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU@TUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU؃ FPUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUa_AUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU=8mUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU@TUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU؃ FPUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUa_AUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU=8mUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU@TUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU؃ FPUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUa_AUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU=8mUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU@TUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU؃ FPUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUa_AUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU=8mUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU@TUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU؃ FPUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUa_AUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU=8mUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU@TUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU؃ FPUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUa_AUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU=8mUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU@TUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU؃ FPUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUa_AUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU=8mUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU@TUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU؃ FPUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUa_AUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU=8mUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU@TUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU؃ FPUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUa_AUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU=8mUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU@TUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU؃ FPUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUa_AUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU=8mUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU@ΰ PK["X" @ large_file.txtPK<N notation-1.2.0/test/e2e/testdata/malicious-plugin/zip_bomb.zip000066400000000000000000001226061466375446100244450ustar00rootroot00000000000000PKlHXRv^dM0PKlHz)V>qv?dM1PKlHX>){Mv dM2PKlHxL)vdM3PKlH>0vcM4PKlHiN9ucM5PKlH(ڽucM6PKlH[ucM7PKlHdϕXuufcM8PKlH!BAQuGcM9PKlHn6-u(cMAPKlH# u cMBPKlHtbMCPKlHQCtbMDPKlHutbMEPKlH`0ytbMFPKlHfDUtnbMGPKlHZ1tObMHPKlHZ; t0bMIPKlHnesbMJPKlH,saMKPKlH3saMLPKlH.}saMMPKlHi\YsaMNPKlH 5svaMOPKlHܯ,bsWaMPPKlHr8aMQPKlH'%jFraMRPKlH6r`MSPKlH`?r`MTPKlHB]r`MUPKlH9r`MVPKlHjqMr~`MWPKlH+q_`MXPKlH(_q@`MYPKlHYq!`MZ PKlHq`M00 PKlH~_q_M01 PKlH I;:q_M02 PKlH/q_M03 PKlH7 p_M04 PKlHTpa_M05 PKlHurkXpA_M06 PKlHF%fp!_M07 PKlH bh\p_M08 PKlH 7p^M09 PKlHGp^M0A PKlH/o^M0B PKlHBh Ho^M0C PKlHaףoa^M0D PKlH+wRl~oA^M0E PKlHCYo!^M0F PKlH`%4o^M0G PKlHXo]M0H PKlHY7n]M0I PKlHNPn]M0J PKlHA Bn]M0K PKlHfx{na]M0L PKlHWVnA]M0M PKlH 1n!]M0N PKlH n]M0O PKlHKm\M0P PKlHvBm\M0Q PKlHǝm\M0R PKlH%xm\M0S PKlH{iSma\M0T PKlH5.mA\M0U PKlHu! m!\M0V PKlHSl\M0W PKlH=xl[M0X PKlHL穚l[M0Y PKlHT9ul[M0Z PKlHXPl[M10 PKlHA5!+la[M11 PKlHlA[M12 PKlH~:&k![M13 PKlH2/Yk[M14 PKlHfkZM15 PKlHtrkZM16 PKlH MkZM17 PKlHf(kZM18 PKlHxkaZM19 PKlH~|jAZM1A PKlH?!,j!ZM1B PKlH =$͔jZM1C PKlHQojYM1D PKlHhJjYM1E PKlH"%jYM1F PKlH0jYM1G PKlH`iaYM1H PKlHqU/iAYM1I PKlH{i!YM1J PKlHȵ5liYM1K PKlH65GiXM1L PKlH D"iXM1M PKlHNhXM1N PKlH'\>hXM1O PKlHhaXM1P PKlHxhAXM1Q PKlHd!3ih!XM1R PKlH oDhXM1S PKlH1hWM1T PKlH%gWM1U PKlHgWM1V PKlHDŽ>gWM1W PKlHNcgaWM1X PKlH ufgAWM1Y PKlHFbWAg!WM1Z PKlH1gWM20 PKlHtnfVM21 PKlHi,fVM22 PKlHAfVM23 PKlHfVM24 PKlH.jGcfaVM25 PKlH_}0>fAVM26 PKlH Ůf!VM27 PKlH6PeVM28 PKlHsiZeUM29 PKlHw9eUM2A PKlHceUM2B PKlH`eUM2C PKlH;eaUM2D PKlH@1eAUM2E PKlHFd!UM2F PKlHdUM2G PKlH`]ۚdTM2H PKlH;dTM2I PKlH1]dTM2J PKlHڶa8dTM2K PKlHV-wdaTM2L PKlH%cATM2M PKlHTRc!TM2N PKlHbXcTM2O PKlHlcSM2P PKlH'}ZcSM2Q PKlH5cSM2R PKlH=cSM2S PKlHbaSM2T PKlHbASM2U PKlH(b!SM2V PKlH`O|bSM2W PKlHi@WbRM2X PKlHr2bRM2Y PKlHҠ bRM2Z PKlHaRM30 PKlH.uaaRM31 PKlH3ȡaARM32 PKlHZya!RM33 PKlH@QTaRM34 PKlH9&/aQM35 PKlHw  aQM36 PKlHk`QM37 PKlH6`QM38 PKlH_R̛`aQM39 PKlHmv`AQM3A PKlHuyQ`!QM3B PKlHp1,`QM3C PKlHUp@`PM3D PKlHO_PM3E PKlH_PM3F PKlH+_PM3G PKlHaMs_aPM3H PKlH8/N_APM3I PKlHw)_!PM3J PKlHEl_PM3K PKlH^OM3L PKlHԠ'F^OM3M PKlHpR}^OM3N PKlHMޤp^OM3O PKlH&K^aOM3P PKlH|&^AOM3Q PKlH(^^!OM3R PKlHRz]OM3S PKlHgI4]NM3T PKlHϒ]NM3U PKlH˲Q*m]NM3V PKlHH]NM3W PKlH1#]aNM3X PKlHJ]\ANM3Y PKlHȯ\!NM3Z PKlHCiG\NM40 PKlH K\MM41 PKlHМj\MM42 PKlHE\MM43 PKlH \MM44 PKlHř,/[aMM45 PKlH0j[AMM46 PKlHSԆ[!MM47 PKlH ]>[MM48 PKlH:g[LM49 PKlHn[?B[LM4A PKlH)*m[LM4B PKlH5SZLM4C PKlH5?ZaLM4D PKlHZALM4E PKlHkZ!LM4F PKlH}dZLM4G PKlHࡏJ?ZKM4H PKlHZKM4I PKlHDYYKM4J PKlHͺYKM4K PKlHqYaKM4L PKlHYAKM4M PKlHl8BaY!KM4N PKlHAXaJM4T PKlHɴ^XAJM4U PKlH9X!JM4V PKlHs6 wXJM4W PKlHdrWIM4X PKlH{*WIM4Y PKlHCWIM4Z PKlHj*WIM50 PKlH[WaIM51 PKlH6WAIM52 PKlHڧW!IM53 PKlHރVIM54 PKlHfmVHM55 PKlHFpVHM56 PKlHh0}VHM57 PKlHdXVHM58 PKlH3VaHM59 PKlHBVAHM5A PKlHR+U!HM5B PKlH )UHM5C PKlH+UGM5D PKlH]4zUGM5E PKlH;UUGM5F PKlHNc0UGM5G PKlHٙ? UaGM5H PKlHTAGM5I PKlH~T!GM5J PKlHTGM5K PKlHbwTFM5L PKlH1RTFM5M PKlHS-TFM5N PKlHTFM5O PKlH ISaFM5P PKlHkGzSAFM5Q PKlH0S!FM5R PKlH,^JtSFM5S PKlHOSEM5T PKlHOo*SEM5U PKlH?~SEM5V PKlH,REM5W PKlHQZRaEM5X0K\d `PKlHXRv^dM0PKlHz)V>qv?dM$1PKlHX>){Mv dMH2PKlHxL)vdMl3PKlH>0vcM4PKlHiN9ucM5PKlH(ڽucM6PKlH[ucM7PKlHdϕXuufcM 8PKlH!BAQuGcMD9PKlHn6-u(cMhAPKlH# u cMBPKlHtbMCPKlHQCtbMDPKlHutbMEPKlH`0ytbMFPKlHfDUtnbM@GPKlHZ1tObMdHPKlHZ; t0bMIPKlHnesbMJPKlH,saMKPKlH3saMLPKlH.}saMMPKlHi\YsaM<NPKlH 5svaM`OPKlHܯ,bsWaMPPKlHr8aMQPKlH'%jFraMRPKlH6r`MSPKlH`?r`MTPKlHB]r`M8UPKlH9r`M\VPKlHjqMr~`MWPKlH+q_`MXPKlH(_q@`MYPKlHYq!`MZPKlHq`M00PKlH~_q_M501PKlH I;:q_MZ02PKlH/q_M03PKlH7 p_M04PKlHTpa_M05PKlHurkXpA_M06PKlHF%fp!_M07PKlH bh\p_M808PKlH 7p^M]09PKlHGp^M0APKlH/o^M0BPKlHBh Ho^M0CPKlHaףoa^M0DPKlH+wRl~oA^M0EPKlHCYo!^M;0FPKlH`%4o^M`0GPKlHXo]M0HPKlHY7n]M0IPKlHNPn]M0JPKlHA Bn]M0KPKlHfx{na]M0LPKlHWVnA]M>0MPKlH 1n!]Mc0NPKlH n]M0OPKlHKm\M0PPKlHvBm\M0QPKlHǝm\M0RPKlH%xm\M 0SPKlH{iSma\MA 0TPKlH5.mA\Mf 0UPKlHu! m!\M 0VPKlHSl\M 0WPKlH=xl[M 0XPKlHL穚l[M 0YPKlHT9ul[M 0ZPKlHXPl[MD 10PKlHA5!+la[Mi 11PKlHlA[M 12PKlH~:&k![M 13PKlH2/Yk[M 14PKlHfkZM 15PKlHtrkZM" 16PKlH MkZMG 17PKlHf(kZMl 18PKlHxkaZM 19PKlH~|jAZM 1APKlH?!,j!ZM 1BPKlH =$͔jZM 1CPKlHQojYM% 1DPKlHhJjYMJ 1EPKlH"%jYMo 1FPKlH0jYM 1GPKlH`iaYM 1HPKlHqU/iAYM 1IPKlH{i!YM 1JPKlHȵ5liYM( 1KPKlH65GiXMM 1LPKlH D"iXMr 1MPKlHNhXM 1NPKlH'\>hXM 1OPKlHhaXM 1PPKlHxhAXM1QPKlHd!3ih!XM+1RPKlH oDhXMP1SPKlH1hWMu1TPKlH%gWM1UPKlHgWM1VPKlHDŽ>gWM1WPKlHNcgaWM 1XPKlH ufgAWM.1YPKlHFbWAg!WMS1ZPKlH1gWMx20PKlHtnfVM21PKlHi,fVM22PKlHAfVM23PKlHfVM 24PKlH.jGcfaVM125PKlH_}0>fAVMV26PKlH Ůf!VM{27PKlH6PeVM28PKlHsiZeUM29PKlHw9eUM2APKlHceUM2BPKlH`eUM42CPKlH;eaUMY2DPKlH@1eAUM~2EPKlHFd!UM2FPKlHdUM2GPKlH`]ۚdTM2HPKlH;dTM2IPKlH1]dTM72JPKlHڶa8dTM\2KPKlHV-wdaTM2LPKlH%cATM2MPKlHTRc!TM2NPKlHbXcTM2OPKlHlcSM2PPKlH'}ZcSM:2QPKlH5cSM_2RPKlH=cSM2SPKlHbaSM2TPKlHbASM2UPKlH(b!SM2VPKlH`O|bSM2WPKlHi@WbRM=2XPKlHr2bRMb2YPKlHҠ bRM2ZPKlHaRM30PKlH.uaaRM31PKlH3ȡaARM32PKlHZya!RM33PKlH@QTaRM@34PKlH9&/aQMe35PKlHw  aQM36PKlHk`QM37PKlH6`QM38PKlH_R̛`aQM39PKlHmv`AQM3APKlHuyQ`!QMC3BPKlHp1,`QMh3CPKlHUp@`PM3DPKlHO_PM3EPKlH_PM3FPKlH+_PM3GPKlHaMs_aPM!3HPKlH8/N_APMF3IPKlHw)_!PMk3JPKlHEl_PM3KPKlH^OM3LPKlHԠ'F^OM3MPKlHpR}^OM3NPKlHMޤp^OM$3OPKlH&K^aOMI3PPKlH|&^AOMn3QPKlH(^^!OM3RPKlHRz]OM3SPKlHgI4]NM3TPKlHϒ]NM3UPKlH˲Q*m]NM'3VPKlHH]NML3WPKlH1#]aNMq3XPKlHJ]\ANM3YPKlHȯ\!NM3ZPKlHCiG\NM40PKlH K\MM41PKlHМj\MM*42PKlHE\MMO43PKlH \MMt44PKlHř,/[aMM45PKlH0j[AMM46PKlHSԆ[!MM47PKlH ]>[MM48PKlH:g[LM-49PKlHn[?B[LMR4APKlH)*m[LMw4BPKlH5SZLM4CPKlH5?ZaLM4DPKlHZALM4EPKlHkZ!LM 4FPKlH}dZLM04GPKlHࡏJ?ZKMU4HPKlHZKMz4IPKlHDYYKM4JPKlHͺYKM4KPKlHqYaKM4LPKlHYAKM4MPKlHl8BaY!KM34NPKlHAXaJM4TPKlHɴ^XAJM64UPKlH9X!JM[4VPKlHs6 wXJM4WPKlHdrWIM4XPKlH{*WIM4YPKlHCWIM4ZPKlHj*WIM50PKlH[WaIM951PKlH6WAIM^52PKlHڧW!IM53PKlHރVIM54PKlHfmVHM55PKlHFpVHM56PKlHh0}VHM 57PKlHdXVHM< 58PKlH3VaHMa 59PKlHBVAHM 5APKlHR+U!HM 5BPKlH )UHM 5CPKlH+UGM 5DPKlH]4zUGM!5EPKlH;UUGM?!5FPKlHNc0UGMd!5GPKlHٙ? UaGM!5HPKlHTAGM!5IPKlH~T!GM!5JPKlHTGM!5KPKlHbwTFM"5LPKlH1RTFMB"5MPKlHS-TFMg"5NPKlHTFM"5OPKlH ISaFM"5PPKlHkGzSAFM"5QPKlH0S!FM"5RPKlH,^JtSFM #5SPKlHOSEME#5TPKlHOo*SEMj#5UPKlH?~SEM#5VPKlH,REM#5WPKlHQZRaEM#5XPK.vnotation-1.2.0/test/e2e/testdata/malicious-plugin/zip_slip.zip000066400000000000000000000003421466375446100244650ustar00rootroot00000000000000PK jt#XR1$../../../../../../../../tmp/evil.txtUT 'e'eux evilPK jt#XR1$../../../../../../../../tmp/evil.txtUT'eux PKjbnotation-1.2.0/test/e2e/testdata/nginx/000077500000000000000000000000001466375446100177535ustar00rootroot00000000000000notation-1.2.0/test/e2e/testdata/nginx/nginx.conf000066400000000000000000000005771466375446100217560ustar00rootroot00000000000000events { worker_connections 1024; } http { server { listen 80; listen 443 ssl; server_name notation-e2e.regisry.io; ssl_certificate /etc/nginx/notation-e2e.registry.io.crt; ssl_certificate_key /etc/nginx/notation-e2e.registry.io.key; location / { proxy_pass http://notation-e2e-registry:5000; } } } notation-1.2.0/test/e2e/testdata/nginx/notation-e2e.registry.io.crt000066400000000000000000000021631466375446100252500ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIIDHDCCAgSgAwIBAgIUQcDBs3e5Z0lR1hUqQZs0diP7CQAwDQYJKoZIhvcNAQEL BQAwIzEhMB8GA1UEAwwYbm90YXRpb24tZTJlLnJlZ2lzdHJ5LmlvMCAXDTIzMDUy MjA5MDAyMFoYDzIxMjMwNDI4MDkwMDIwWjAjMSEwHwYDVQQDDBhub3RhdGlvbi1l MmUucmVnaXN0cnkuaW8wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDn LxBVN7iawG8Q5MD1Fczv0Zr1QG5pRnz5DXo2hLSnrJ2xYiXWRgIlfI67adMPiF3v LZXKVJtVkkYAL/0iLu6YpIHltmzXo+/rY3RV+jk0Lj380Zfp5gC6SLXIKuzM9AnT g3pkOt7zBHQP0xOcK2aPuhPEySuSoGQ6jupWFD3vPBgvcW7+sF0NUHdTnN6dz0sR dmlbEDmaYJ+weZa0Skvc1Mc2znnJmWZWY6PlC7SPQey6MC5CTjukT8AVMqwedjfv fQ8vOhvxu+UFnncEqwM7f83hiYD4QWK/ZA5iIobpd0aP/83R87WkGNyHypnWbmEb C7Py8OWZoFKqcBHj13c/AgMBAAGjRjBEMCMGA1UdEQQcMBqCGG5vdGF0aW9uLWUy ZS5yZWdpc3RyeS5pbzAdBgNVHQ4EFgQUCNOgP6vTy0+IO3UA1mZ9G8pAHqQwDQYJ KoZIhvcNAQELBQADggEBAIofq+60DleGTdxr4RqvaXytIbNX57TrAjUvFwFy9E3e mxmy14vHkwOLRoCMbbP4wy+XG2I1r7NcmhDxGc9z9z+YrhuW9IDWSxv8+KYWM5T/ 7viqHJtyYk5eMpN+k+7qB8kXoahnrgOZjmdDwJBsj6kwrcnuqvwRBi3fxeNJl+/Q tDOEPyqIybWMw43tTvE33k0k8ia7nEk0slcURdLVllAfTC6DdL2SJtELD8SMvPPh nwYKU6dGbYlnWVRrfqQG3PCksuLbRzX+zQCAMlSgtD+KSpjyxkaazPY/SD4gLav7 u+96xpZVLbrsjklcrT2dEku3zprnM63P8DnV/wHDdUc= -----END CERTIFICATE----- notation-1.2.0/test/e2e/testdata/nginx/notation-e2e.registry.io.key000066400000000000000000000032501466375446100252460ustar00rootroot00000000000000-----BEGIN PRIVATE KEY----- MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDnLxBVN7iawG8Q 5MD1Fczv0Zr1QG5pRnz5DXo2hLSnrJ2xYiXWRgIlfI67adMPiF3vLZXKVJtVkkYA L/0iLu6YpIHltmzXo+/rY3RV+jk0Lj380Zfp5gC6SLXIKuzM9AnTg3pkOt7zBHQP 0xOcK2aPuhPEySuSoGQ6jupWFD3vPBgvcW7+sF0NUHdTnN6dz0sRdmlbEDmaYJ+w eZa0Skvc1Mc2znnJmWZWY6PlC7SPQey6MC5CTjukT8AVMqwedjfvfQ8vOhvxu+UF nncEqwM7f83hiYD4QWK/ZA5iIobpd0aP/83R87WkGNyHypnWbmEbC7Py8OWZoFKq cBHj13c/AgMBAAECggEAQhYnnpy/pmlVNqiV6lnRjErId8xz459VUWLDaXtVI0uK hq8ubsrziSDKspOFVL7gT2OiGsVF5Ffcr+gH/jIZXcRFJ9QW2CwShSEYnA1cNej0 KmYF/cSUt6vaXz66E7q9ZlwC7E0R97lxriZiSDX16yc/yHTTgmZcUIsTPQkrTUw+ 7IgY0uV3HFCnGU5i5jeB+UF5MIPauBPt3YQcbBythwkZadxaKI/1Mp1vn4RcdzMg +XJ4GMND1A1qzn3mQm5PqBMk4NC+FVcBxvdxzZum7qu1iLap0e3fyD9PqFTb1Zix 3976Qj/QrttbO1WXDtBvEaXqlGVbEDR1cpKQKYghwQKBgQDx+GkdBb6lYGDERnZC pY65s0k2HVnZ6BxFGJXMryWN8CYMXKgns0O4idIYQQnzWFUmudxfAy13H3wukXDC aHF2YZ1QuqOmc3kIkxKVD+9QfcgKGfLAGemyFtwhSbegk6MKF4UOqDNL9unvDdAo 7j9rjq5IOqfgPorqm1AoySgDQQKBgQD0loxIEM/3O5PqT8sda5G9bsXh9QlV3Za0 XSVYlkKxXuPIlC0jmDHk/xI41ykdrqjwmYd2FOt7Luuevke/ahqQSbXxy0LFdR5J QJ6iN3OMZMsXMImRCoLD0kEvh+Z1u4VUiHuXmBQtiv5uP9S1KAETL44SEZyfJn63 uvNMlplafwKBgENb58cQhlX7UnTROLKs6+J+Km9KFG041EXX5juotkehBraCRL1o hf2lQDtIP8DiYjH5o4M/mzSCK0u7aSx1bsCJxAVpL41yr8rXRmEAoppBqaJGPvGD RS8yde0+XEPzVXvFuGCwKjeHcO//ZGdAi58hhRrOWVVvk7RjsBjqhp0BAoGBAJv5 WZInbofKLYSRyASF8ZWtC3IR8hcYzR9N+x/oCrXTvkzN+Y8mYkMXSkaHJ0gvdrqg HZt2sciHXmiIDXcKsc/rwaRlK7qB+oNaOw9Vb1FLgZvTLxcYbdV0wm8OKjBQGjGT K8W7jLqSVbh26i1wSmcyv1XUd12ijdKa3MatjzP/AoGARuw3wa+b0GCH5jtzJMBs 9N/Tr9lGLHH7Q57c4nAR2rR5L0DGAns5XWN9yKCyIDFFh89kLGNRwj/cONY3nXud /1l7seup811NusPiBaZC+5LX/zAvzH0orFzinEdV7V4g435j/i52V5Bl5fJVw7FM CfqmotJm0hceUk1QaStn+Ds= -----END PRIVATE KEY----- notation-1.2.0/test/e2e/testdata/registry/000077500000000000000000000000001466375446100205005ustar00rootroot00000000000000notation-1.2.0/test/e2e/testdata/registry/oci_layout/000077500000000000000000000000001466375446100226475ustar00rootroot00000000000000notation-1.2.0/test/e2e/testdata/registry/oci_layout/e2e-expired-signature/000077500000000000000000000000001466375446100267575ustar00rootroot00000000000000notation-1.2.0/test/e2e/testdata/registry/oci_layout/e2e-expired-signature/blobs/000077500000000000000000000000001466375446100300605ustar00rootroot00000000000000notation-1.2.0/test/e2e/testdata/registry/oci_layout/e2e-expired-signature/blobs/sha256/000077500000000000000000000000001466375446100310705ustar00rootroot000000000000003829708badae8e4b14e1adbc2fac7182bc0c27c5b6f54153e8b68a12eab10a1e000066400000000000000000000013301466375446100421320ustar00rootroot00000000000000notation-1.2.0/test/e2e/testdata/registry/oci_layout/e2e-expired-signature/blobs/sha256{"schemaVersion":2,"mediaType":"application/vnd.oci.image.manifest.v1+json","config":{"mediaType":"application/vnd.cncf.notary.signature","digest":"sha256:44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a","size":2},"layers":[{"mediaType":"application/jose+json","digest":"sha256:b5f927564088f65dad86c81bcdd0b55415a8b4c9a0a5391107210e0cdb305cc7","size":2171}],"subject":{"mediaType":"application/vnd.oci.image.manifest.v1+json","digest":"sha256:b8479de3f88fb259a0a9ea82a5b2a052a1ef3c4ebbcfc61482d5ae4c831f8af9","size":517},"annotations":{"io.cncf.notary.x509chain.thumbprint#S256":"[\"e38aea13f2b051f7183cca64a51ee676f4d4456f41d9fecf15eb367efc7343b9\"]","org.opencontainers.image.created":"2023-06-27T05:20:21Z"}}44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a000066400000000000000000000000021466375446100416150ustar00rootroot00000000000000notation-1.2.0/test/e2e/testdata/registry/oci_layout/e2e-expired-signature/blobs/sha256{}6de5fe1f75e4b1094c98086f403d5fd8117776ebe5aad1609281403d13dea070000066400000000000000000000000211466375446100414500ustar00rootroot00000000000000notation-1.2.0/test/e2e/testdata/registry/oci_layout/e2e-expired-signature/blobs/sha256awesome notation 9567ef95679c34ba6deedf8ff1a43e17f889f83ee4276c070f7f5c97bad66030000066400000000000000000000007451466375446100417700ustar00rootroot00000000000000notation-1.2.0/test/e2e/testdata/registry/oci_layout/e2e-expired-signature/blobs/sha256{"schemaVersion":2,"mediaType":"application/vnd.oci.image.index.v1+json","manifests":[{"mediaType":"application/vnd.oci.image.manifest.v1+json","digest":"sha256:3829708badae8e4b14e1adbc2fac7182bc0c27c5b6f54153e8b68a12eab10a1e","size":728,"annotations":{"io.cncf.notary.x509chain.thumbprint#S256":"[\"e38aea13f2b051f7183cca64a51ee676f4d4456f41d9fecf15eb367efc7343b9\"]","org.opencontainers.image.created":"2023-06-27T05:20:21Z"},"artifactType":"application/vnd.cncf.notary.signature"}]}b5f927564088f65dad86c81bcdd0b55415a8b4c9a0a5391107210e0cdb305cc7000066400000000000000000000041731466375446100415220ustar00rootroot00000000000000notation-1.2.0/test/e2e/testdata/registry/oci_layout/e2e-expired-signature/blobs/sha256{"payload":"eyJ0YXJnZXRBcnRpZmFjdCI6eyJkaWdlc3QiOiJzaGEyNTY6Yjg0NzlkZTNmODhmYjI1OWEwYTllYTgyYTViMmEwNTJhMWVmM2M0ZWJiY2ZjNjE0ODJkNWFlNGM4MzFmOGFmOSIsIm1lZGlhVHlwZSI6ImFwcGxpY2F0aW9uL3ZuZC5vY2kuaW1hZ2UubWFuaWZlc3QudjEranNvbiIsInNpemUiOjUxN319","protected":"eyJhbGciOiJQUzI1NiIsImNyaXQiOlsiaW8uY25jZi5ub3Rhcnkuc2lnbmluZ1NjaGVtZSIsImlvLmNuY2Yubm90YXJ5LmV4cGlyeSJdLCJjdHkiOiJhcHBsaWNhdGlvbi92bmQuY25jZi5ub3RhcnkucGF5bG9hZC52MStqc29uIiwiaW8uY25jZi5ub3RhcnkuZXhwaXJ5IjoiMjAyMy0wNi0yN1QxMzoyMToyMSswODowMCIsImlvLmNuY2Yubm90YXJ5LnNpZ25pbmdTY2hlbWUiOiJub3RhcnkueDUwOSIsImlvLmNuY2Yubm90YXJ5LnNpZ25pbmdUaW1lIjoiMjAyMy0wNi0yN1QxMzoyMDoyMSswODowMCJ9","header":{"x5c":["MIIDOjCCAiKgAwIBAgIBUTANBgkqhkiG9w0BAQsFADBLMQswCQYDVQQGEwJVUzELMAkGA1UECBMCV0ExEDAOBgNVBAcTB1NlYXR0bGUxDzANBgNVBAoTBk5vdGFyeTEMMAoGA1UEAxMDZTJlMCAXDTIyMTIxOTAyNDMzOVoYDzIxMjIxMjE5MDI0MzM5WjBLMQswCQYDVQQGEwJVUzELMAkGA1UECBMCV0ExEDAOBgNVBAcTB1NlYXR0bGUxDzANBgNVBAoTBk5vdGFyeTEMMAoGA1UEAxMDZTJlMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwNrKIzpESDkY7Kah+sWKeXDNE7yUBaqW58cyMtkpk9/Q6QLbndMxdt8VjD3y4bSBhZXLdUkW0S4hSNZXQwkYm3yooxJXj+uOI9NnEPt/gAkPYri4TdKpTFSBzE4uJ0HrYDBUir3Dars/CXiusmdCnSWKsgm05ItkHdHEGtor716aWdnGuFNvyzJyaC3XLFpo1OwEwTyxf4Yix4UtvwNDB4TOJRH84avSoFCua8xbRpiBJ0QoX6zY0Yr9qbvMBQIcmpQ3uWOiEeVMDjRE0XzhiWSevVfZTPJcYpsAyBTJGJLIcHU7JMYnwKP6IzUg5T589lvRVD7up4sAC7izOKGzPQIDAQABoycwJTAOBgNVHQ8BAf8EBAMCB4AwEwYDVR0lBAwwCgYIKwYBBQUHAwMwDQYJKoZIhvcNAQELBQADggEBAKacaJ+O9rTIdfLHCdneLQ6RN92Id5dl8PGh/Xig3AWSOtLgIiRRsdxVH5PZkGOHosellQG5fCvQOwB0x8O+2YDKLOfgVIJWd6X85NdvyCdX2ElYRmvX9ON5WVguGLluwkOfJ26M8d8ftXrcc97qaKk4EHS8R/LCWqZNDRiRCA0OtNP9cUkKFaIG1hgWgEieVWnxrCyUDbTX3uCiJKNzSOmI3psF3WIhabU7/7gBm95nWhgwS91qAxavccVkY6hqAPj9tjJH5UPI5RR8kDB5rWiCIx1YHuH+z1eAYUHWVvZvneVniNBI8qGoGBz9HkrX5ecf+V7zaxyy0FoWlEX1z8k="],"io.cncf.notary.signingAgent":"Notation/1.0.0"},"signature":"OPdBj-46T0vYIyrxz4mtD0OFCMOn6rdqy8qMe4n2ShFGtcWqrA4LZbMTO5fBLRw-Z7SA6DCHKGP_sNBAMRC62R5TyMD53M8UmDweJs7rCQyBbATlTqXQFlLO8OCvANdASKPgQYsbADkVimA5Tp4srCxUyTT64a0vzCrgdCBcd_GIX9u-sNuVOvwQrRvHrA4-GPvTkwNoRDrXhVcgN_5IDpGLvAqHumPl-g7tZ-NrMLljzcdJ6uKDBJhAH4HRc64SnkPmTNWqNfzt9kIa9T3LBDSqVUJGw84BotRisz7Mj3it7BDnCh_5sJWBW-czYzUYu51FrRjLGRLEZM2kqcXF1w"}b8479de3f88fb259a0a9ea82a5b2a052a1ef3c4ebbcfc61482d5ae4c831f8af9000066400000000000000000000010051466375446100422550ustar00rootroot00000000000000notation-1.2.0/test/e2e/testdata/registry/oci_layout/e2e-expired-signature/blobs/sha256{"schemaVersion":2,"mediaType":"application/vnd.oci.image.manifest.v1+json","config":{"mediaType":"application/vnd.notation.config","digest":"sha256:44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a","size":2},"layers":[{"mediaType":"application/vnd.oci.image.layer.v1.tar","digest":"sha256:6de5fe1f75e4b1094c98086f403d5fd8117776ebe5aad1609281403d13dea070","size":17,"annotations":{"org.opencontainers.image.title":"e2e.txt"}}],"annotations":{"org.opencontainers.image.created":"2023-06-27T05:01:36Z"}}notation-1.2.0/test/e2e/testdata/registry/oci_layout/e2e-expired-signature/index.json000066400000000000000000000012401466375446100307560ustar00rootroot00000000000000{"schemaVersion":2,"manifests":[{"mediaType":"application/vnd.oci.image.manifest.v1+json","digest":"sha256:b8479de3f88fb259a0a9ea82a5b2a052a1ef3c4ebbcfc61482d5ae4c831f8af9","size":517,"annotations":{"org.opencontainers.image.ref.name":"v1"}},{"mediaType":"application/vnd.oci.image.manifest.v1+json","digest":"sha256:3829708badae8e4b14e1adbc2fac7182bc0c27c5b6f54153e8b68a12eab10a1e","size":728},{"mediaType":"application/vnd.oci.image.index.v1+json","digest":"sha256:9567ef95679c34ba6deedf8ff1a43e17f889f83ee4276c070f7f5c97bad66030","size":485,"annotations":{"org.opencontainers.image.ref.name":"sha256-b8479de3f88fb259a0a9ea82a5b2a052a1ef3c4ebbcfc61482d5ae4c831f8af9"}}]}notation-1.2.0/test/e2e/testdata/registry/oci_layout/e2e-expired-signature/oci-layout000066400000000000000000000000361466375446100307660ustar00rootroot00000000000000{"imageLayoutVersion":"1.0.0"}notation-1.2.0/test/e2e/testdata/registry/oci_layout/e2e-invalid-signature/000077500000000000000000000000001466375446100267455ustar00rootroot00000000000000notation-1.2.0/test/e2e/testdata/registry/oci_layout/e2e-invalid-signature/blobs/000077500000000000000000000000001466375446100300465ustar00rootroot00000000000000notation-1.2.0/test/e2e/testdata/registry/oci_layout/e2e-invalid-signature/blobs/sha256/000077500000000000000000000000001466375446100310565ustar00rootroot00000000000000321fcc674d6771dd0e5bd21e599f2df3a482de99a400fd3a3fe371221b97a9c9000066400000000000000000000012741466375446100417000ustar00rootroot00000000000000notation-1.2.0/test/e2e/testdata/registry/oci_layout/e2e-invalid-signature/blobs/sha256{"schemaVersion":2,"mediaType":"application/vnd.oci.image.manifest.v1+json","config":{"mediaType":"application/vnd.cncf.notary.signature","digest":"sha256:44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a","size":2},"layers":[{"mediaType":"application/vnd.oci.image.layer.v1.tar","digest":"sha256:9a8decdf3775708f4eed16859f19561d730cad40b1af0dcfc37ac1e5f879a934","size":18,"annotations":{"org.opencontainers.image.title":"invalid_signature.txt"}}],"subject":{"mediaType":"application/vnd.oci.image.manifest.v1+json","digest":"sha256:b8479de3f88fb259a0a9ea82a5b2a052a1ef3c4ebbcfc61482d5ae4c831f8af9","size":517},"annotations":{"org.opencontainers.image.created":"2023-06-27T05:57:43Z"}}44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a000066400000000000000000000000021466375446100416030ustar00rootroot00000000000000notation-1.2.0/test/e2e/testdata/registry/oci_layout/e2e-invalid-signature/blobs/sha256{}6de5fe1f75e4b1094c98086f403d5fd8117776ebe5aad1609281403d13dea070000066400000000000000000000000211466375446100414360ustar00rootroot00000000000000notation-1.2.0/test/e2e/testdata/registry/oci_layout/e2e-invalid-signature/blobs/sha256awesome notation 9a8decdf3775708f4eed16859f19561d730cad40b1af0dcfc37ac1e5f879a934000066400000000000000000000000221466375446100420450ustar00rootroot00000000000000notation-1.2.0/test/e2e/testdata/registry/oci_layout/e2e-invalid-signature/blobs/sha256invalid signature a045c6c6d90c1183b27b6759500aa5996b90701759d4d149f7b540e59f0b632a000066400000000000000000000005611466375446100411510ustar00rootroot00000000000000notation-1.2.0/test/e2e/testdata/registry/oci_layout/e2e-invalid-signature/blobs/sha256{"schemaVersion":2,"mediaType":"application/vnd.oci.image.index.v1+json","manifests":[{"mediaType":"application/vnd.oci.image.manifest.v1+json","digest":"sha256:321fcc674d6771dd0e5bd21e599f2df3a482de99a400fd3a3fe371221b97a9c9","size":700,"annotations":{"org.opencontainers.image.created":"2023-06-27T05:57:43Z"},"artifactType":"application/vnd.cncf.notary.signature"}]}b8479de3f88fb259a0a9ea82a5b2a052a1ef3c4ebbcfc61482d5ae4c831f8af9000066400000000000000000000010051466375446100422430ustar00rootroot00000000000000notation-1.2.0/test/e2e/testdata/registry/oci_layout/e2e-invalid-signature/blobs/sha256{"schemaVersion":2,"mediaType":"application/vnd.oci.image.manifest.v1+json","config":{"mediaType":"application/vnd.notation.config","digest":"sha256:44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a","size":2},"layers":[{"mediaType":"application/vnd.oci.image.layer.v1.tar","digest":"sha256:6de5fe1f75e4b1094c98086f403d5fd8117776ebe5aad1609281403d13dea070","size":17,"annotations":{"org.opencontainers.image.title":"e2e.txt"}}],"annotations":{"org.opencontainers.image.created":"2023-06-27T05:01:36Z"}}notation-1.2.0/test/e2e/testdata/registry/oci_layout/e2e-invalid-signature/index.json000066400000000000000000000012401466375446100307440ustar00rootroot00000000000000{"schemaVersion":2,"manifests":[{"mediaType":"application/vnd.oci.image.manifest.v1+json","digest":"sha256:b8479de3f88fb259a0a9ea82a5b2a052a1ef3c4ebbcfc61482d5ae4c831f8af9","size":517,"annotations":{"org.opencontainers.image.ref.name":"v1"}},{"mediaType":"application/vnd.oci.image.manifest.v1+json","digest":"sha256:321fcc674d6771dd0e5bd21e599f2df3a482de99a400fd3a3fe371221b97a9c9","size":700},{"mediaType":"application/vnd.oci.image.index.v1+json","digest":"sha256:a045c6c6d90c1183b27b6759500aa5996b90701759d4d149f7b540e59f0b632a","size":369,"annotations":{"org.opencontainers.image.ref.name":"sha256-b8479de3f88fb259a0a9ea82a5b2a052a1ef3c4ebbcfc61482d5ae4c831f8af9"}}]}notation-1.2.0/test/e2e/testdata/registry/oci_layout/e2e-invalid-signature/oci-layout000066400000000000000000000000361466375446100307540ustar00rootroot00000000000000{"imageLayoutVersion":"1.0.0"}notation-1.2.0/test/e2e/testdata/registry/oci_layout/e2e-valid-signature/000077500000000000000000000000001466375446100264165ustar00rootroot00000000000000notation-1.2.0/test/e2e/testdata/registry/oci_layout/e2e-valid-signature/blobs/000077500000000000000000000000001466375446100275175ustar00rootroot00000000000000notation-1.2.0/test/e2e/testdata/registry/oci_layout/e2e-valid-signature/blobs/sha256/000077500000000000000000000000001466375446100305275ustar00rootroot0000000000000013e107e0dd246f07ff18998a3d39e226965c41477a1bc1765f7617632ff3f283000066400000000000000000000007451466375446100406510ustar00rootroot00000000000000notation-1.2.0/test/e2e/testdata/registry/oci_layout/e2e-valid-signature/blobs/sha256{"schemaVersion":2,"mediaType":"application/vnd.oci.image.index.v1+json","manifests":[{"mediaType":"application/vnd.oci.image.manifest.v1+json","digest":"sha256:90ceaff260d657d797c408ac73564a9c7bb9d86055877c2a811f0e63b8c6524f","size":728,"annotations":{"io.cncf.notary.x509chain.thumbprint#S256":"[\"e38aea13f2b051f7183cca64a51ee676f4d4456f41d9fecf15eb367efc7343b9\"]","org.opencontainers.image.created":"2023-06-27T05:30:33Z"},"artifactType":"application/vnd.cncf.notary.signature"}]}44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a000066400000000000000000000000021466375446100412540ustar00rootroot00000000000000notation-1.2.0/test/e2e/testdata/registry/oci_layout/e2e-valid-signature/blobs/sha256{}6de5fe1f75e4b1094c98086f403d5fd8117776ebe5aad1609281403d13dea070000066400000000000000000000000211466375446100411070ustar00rootroot00000000000000notation-1.2.0/test/e2e/testdata/registry/oci_layout/e2e-valid-signature/blobs/sha256awesome notation 90ceaff260d657d797c408ac73564a9c7bb9d86055877c2a811f0e63b8c6524f000066400000000000000000000013301466375446100411450ustar00rootroot00000000000000notation-1.2.0/test/e2e/testdata/registry/oci_layout/e2e-valid-signature/blobs/sha256{"schemaVersion":2,"mediaType":"application/vnd.oci.image.manifest.v1+json","config":{"mediaType":"application/vnd.cncf.notary.signature","digest":"sha256:44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a","size":2},"layers":[{"mediaType":"application/jose+json","digest":"sha256:c9af07e038b2b1a463e648986ad7273157511ffeba0b689f33e5472bae471f50","size":2070}],"subject":{"mediaType":"application/vnd.oci.image.manifest.v1+json","digest":"sha256:b8479de3f88fb259a0a9ea82a5b2a052a1ef3c4ebbcfc61482d5ae4c831f8af9","size":517},"annotations":{"io.cncf.notary.x509chain.thumbprint#S256":"[\"e38aea13f2b051f7183cca64a51ee676f4d4456f41d9fecf15eb367efc7343b9\"]","org.opencontainers.image.created":"2023-06-27T05:30:33Z"}}b8479de3f88fb259a0a9ea82a5b2a052a1ef3c4ebbcfc61482d5ae4c831f8af9000066400000000000000000000010051466375446100417140ustar00rootroot00000000000000notation-1.2.0/test/e2e/testdata/registry/oci_layout/e2e-valid-signature/blobs/sha256{"schemaVersion":2,"mediaType":"application/vnd.oci.image.manifest.v1+json","config":{"mediaType":"application/vnd.notation.config","digest":"sha256:44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a","size":2},"layers":[{"mediaType":"application/vnd.oci.image.layer.v1.tar","digest":"sha256:6de5fe1f75e4b1094c98086f403d5fd8117776ebe5aad1609281403d13dea070","size":17,"annotations":{"org.opencontainers.image.title":"e2e.txt"}}],"annotations":{"org.opencontainers.image.created":"2023-06-27T05:01:36Z"}}c9af07e038b2b1a463e648986ad7273157511ffeba0b689f33e5472bae471f50000066400000000000000000000040261466375446100411240ustar00rootroot00000000000000notation-1.2.0/test/e2e/testdata/registry/oci_layout/e2e-valid-signature/blobs/sha256{"payload":"eyJ0YXJnZXRBcnRpZmFjdCI6eyJkaWdlc3QiOiJzaGEyNTY6Yjg0NzlkZTNmODhmYjI1OWEwYTllYTgyYTViMmEwNTJhMWVmM2M0ZWJiY2ZjNjE0ODJkNWFlNGM4MzFmOGFmOSIsIm1lZGlhVHlwZSI6ImFwcGxpY2F0aW9uL3ZuZC5vY2kuaW1hZ2UubWFuaWZlc3QudjEranNvbiIsInNpemUiOjUxN319","protected":"eyJhbGciOiJQUzI1NiIsImNyaXQiOlsiaW8uY25jZi5ub3Rhcnkuc2lnbmluZ1NjaGVtZSJdLCJjdHkiOiJhcHBsaWNhdGlvbi92bmQuY25jZi5ub3RhcnkucGF5bG9hZC52MStqc29uIiwiaW8uY25jZi5ub3Rhcnkuc2lnbmluZ1NjaGVtZSI6Im5vdGFyeS54NTA5IiwiaW8uY25jZi5ub3Rhcnkuc2lnbmluZ1RpbWUiOiIyMDIzLTA2LTI3VDEzOjMwOjMzKzA4OjAwIn0","header":{"x5c":["MIIDOjCCAiKgAwIBAgIBUTANBgkqhkiG9w0BAQsFADBLMQswCQYDVQQGEwJVUzELMAkGA1UECBMCV0ExEDAOBgNVBAcTB1NlYXR0bGUxDzANBgNVBAoTBk5vdGFyeTEMMAoGA1UEAxMDZTJlMCAXDTIyMTIxOTAyNDMzOVoYDzIxMjIxMjE5MDI0MzM5WjBLMQswCQYDVQQGEwJVUzELMAkGA1UECBMCV0ExEDAOBgNVBAcTB1NlYXR0bGUxDzANBgNVBAoTBk5vdGFyeTEMMAoGA1UEAxMDZTJlMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwNrKIzpESDkY7Kah+sWKeXDNE7yUBaqW58cyMtkpk9/Q6QLbndMxdt8VjD3y4bSBhZXLdUkW0S4hSNZXQwkYm3yooxJXj+uOI9NnEPt/gAkPYri4TdKpTFSBzE4uJ0HrYDBUir3Dars/CXiusmdCnSWKsgm05ItkHdHEGtor716aWdnGuFNvyzJyaC3XLFpo1OwEwTyxf4Yix4UtvwNDB4TOJRH84avSoFCua8xbRpiBJ0QoX6zY0Yr9qbvMBQIcmpQ3uWOiEeVMDjRE0XzhiWSevVfZTPJcYpsAyBTJGJLIcHU7JMYnwKP6IzUg5T589lvRVD7up4sAC7izOKGzPQIDAQABoycwJTAOBgNVHQ8BAf8EBAMCB4AwEwYDVR0lBAwwCgYIKwYBBQUHAwMwDQYJKoZIhvcNAQELBQADggEBAKacaJ+O9rTIdfLHCdneLQ6RN92Id5dl8PGh/Xig3AWSOtLgIiRRsdxVH5PZkGOHosellQG5fCvQOwB0x8O+2YDKLOfgVIJWd6X85NdvyCdX2ElYRmvX9ON5WVguGLluwkOfJ26M8d8ftXrcc97qaKk4EHS8R/LCWqZNDRiRCA0OtNP9cUkKFaIG1hgWgEieVWnxrCyUDbTX3uCiJKNzSOmI3psF3WIhabU7/7gBm95nWhgwS91qAxavccVkY6hqAPj9tjJH5UPI5RR8kDB5rWiCIx1YHuH+z1eAYUHWVvZvneVniNBI8qGoGBz9HkrX5ecf+V7zaxyy0FoWlEX1z8k="],"io.cncf.notary.signingAgent":"Notation/1.0.0"},"signature":"vJnEBEYvmRaz9jpOKJzGGorX7COBww5KzXVLuZLeB0ponDApf15r4WhajjVXRlbh2qyxZP3v3PkR8-SVh7ku1SDQ416H1uNNjkKJdovnu-CNAdAborfJCUzTLRDyioUXgp8y098dyPabWNo_hNJautZVvQkerEWLul5c40SclrruKtrz1dK2w3BIcDQR6hOIlQ2VvwnmsCkzDbkljlWGwwjR9xfpePhzAMP4K9MQc0KR4Vmu3Qd9aBdxtI9NW477B2XC2jSJ6erh74HCDtJcIpVxI30i-2JXKvSlrATGGR_Tj5x8vFgpkHGmbIEJFLzjqM347O_Q0A5SLIW-rWE2WQ"}notation-1.2.0/test/e2e/testdata/registry/oci_layout/e2e-valid-signature/index.json000066400000000000000000000012401466375446100304150ustar00rootroot00000000000000{"schemaVersion":2,"manifests":[{"mediaType":"application/vnd.oci.image.manifest.v1+json","digest":"sha256:b8479de3f88fb259a0a9ea82a5b2a052a1ef3c4ebbcfc61482d5ae4c831f8af9","size":517,"annotations":{"org.opencontainers.image.ref.name":"v1"}},{"mediaType":"application/vnd.oci.image.manifest.v1+json","digest":"sha256:90ceaff260d657d797c408ac73564a9c7bb9d86055877c2a811f0e63b8c6524f","size":728},{"mediaType":"application/vnd.oci.image.index.v1+json","digest":"sha256:13e107e0dd246f07ff18998a3d39e226965c41477a1bc1765f7617632ff3f283","size":485,"annotations":{"org.opencontainers.image.ref.name":"sha256-b8479de3f88fb259a0a9ea82a5b2a052a1ef3c4ebbcfc61482d5ae4c831f8af9"}}]}notation-1.2.0/test/e2e/testdata/registry/oci_layout/e2e-valid-signature/oci-layout000066400000000000000000000000361466375446100304250ustar00rootroot00000000000000{"imageLayoutVersion":"1.0.0"}notation-1.2.0/test/e2e/testdata/registry/oci_layout/e2e-with-expired-cert/000077500000000000000000000000001466375446100266645ustar00rootroot00000000000000notation-1.2.0/test/e2e/testdata/registry/oci_layout/e2e-with-expired-cert/blobs/000077500000000000000000000000001466375446100277655ustar00rootroot00000000000000notation-1.2.0/test/e2e/testdata/registry/oci_layout/e2e-with-expired-cert/blobs/sha256/000077500000000000000000000000001466375446100307755ustar00rootroot000000000000002396c911429d6ca56ad6c98143e38e319d90165aa43c2089c9078905253b0f6c000066400000000000000000000007451466375446100410250ustar00rootroot00000000000000notation-1.2.0/test/e2e/testdata/registry/oci_layout/e2e-with-expired-cert/blobs/sha256{"schemaVersion":2,"mediaType":"application/vnd.oci.image.index.v1+json","manifests":[{"mediaType":"application/vnd.oci.image.manifest.v1+json","digest":"sha256:b06e392c2d88206b492dbaf5381800578611eb24f86587bc7717cd3b736e5da7","size":728,"annotations":{"io.cncf.notary.x509chain.thumbprint#S256":"[\"d9927f0d5c45c1bc4206b8c458306bb14b22d594d5086aaff71e1a1443a64632\"]","org.opencontainers.image.created":"2023-06-27T06:07:26Z"},"artifactType":"application/vnd.cncf.notary.signature"}]}44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a000066400000000000000000000000021466375446100415220ustar00rootroot00000000000000notation-1.2.0/test/e2e/testdata/registry/oci_layout/e2e-with-expired-cert/blobs/sha256{}6de5fe1f75e4b1094c98086f403d5fd8117776ebe5aad1609281403d13dea070000066400000000000000000000000211466375446100413550ustar00rootroot00000000000000notation-1.2.0/test/e2e/testdata/registry/oci_layout/e2e-with-expired-cert/blobs/sha256awesome notation 8513a5ba473f32b78a87518b732700632c05cde0d59d179275c5c5a8096beeed000066400000000000000000000041521466375446100413120ustar00rootroot00000000000000notation-1.2.0/test/e2e/testdata/registry/oci_layout/e2e-with-expired-cert/blobs/sha256{"payload":"eyJ0YXJnZXRBcnRpZmFjdCI6eyJkaWdlc3QiOiJzaGEyNTY6Yjg0NzlkZTNmODhmYjI1OWEwYTllYTgyYTViMmEwNTJhMWVmM2M0ZWJiY2ZjNjE0ODJkNWFlNGM4MzFmOGFmOSIsIm1lZGlhVHlwZSI6ImFwcGxpY2F0aW9uL3ZuZC5vY2kuaW1hZ2UubWFuaWZlc3QudjEranNvbiIsInNpemUiOjUxN319","protected":"eyJhbGciOiJQUzI1NiIsImNyaXQiOlsiaW8uY25jZi5ub3Rhcnkuc2lnbmluZ1NjaGVtZSJdLCJjdHkiOiJhcHBsaWNhdGlvbi92bmQuY25jZi5ub3RhcnkucGF5bG9hZC52MStqc29uIiwiaW8uY25jZi5ub3Rhcnkuc2lnbmluZ1NjaGVtZSI6Im5vdGFyeS54NTA5IiwiaW8uY25jZi5ub3Rhcnkuc2lnbmluZ1RpbWUiOiIyMDIzLTA2LTI3VDE0OjA3OjI2KzA4OjAwIn0","header":{"x5c":["MIIDeDCCAmCgAwIBAgIUOmmr4z226FR1IU/HHg29TVLTYk8wDQYJKoZIhvcNAQELBQAwRTEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMRMwEQYDVQQIDApTb21lLVN0YXRlMQswCQYDVQQGEwJBVTAeFw0yMzA2MjYwNjEwMDBaFw0yMzA2MjcwNjEwMDBaMEUxITAfBgNVBAoMGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDETMBEGA1UECAwKU29tZS1TdGF0ZTELMAkGA1UEBhMCQVUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDAm29HFRPQOpSgfjdYGmX+/UgE2vD36Y5U9AwEyMGPWAgHdRCCFPe0RkQic15HjwZRp9AkaJN+SGdkcpjaYdWVphBeDInu+9o7CmhaA5z8yonm7UfQVO2813AZoXOdAyhiuVTN2CqPAhRk94yCONVtWrCsQqgEuPqSl61sB6+4t2EY20xPFGsGJu/y9D5ihnqBsIAcohVh3mRTcQ3IRi1VvWiPApCD9CN5eyKZO07IRNANeKRn+yX+o0M7pDfL5b6lZhHTxihhfZXSw/9JEDbucBzWLvQ8nvn/VA+CNCQOOTcCfPCYTjTeCvyuskxwJJ1p3KNdHMgP28RsQiIq4ynfAgMBAAGjYDBeMB0GA1UdDgQWBBR8Nz1AyK8YixMB+6CaZzVIk9NQ4zAfBgNVHSMEGDAWgBR8Nz1AyK8YixMB+6CaZzVIk9NQ4zAMBgNVHRMBAf8EAjAAMA4GA1UdDwEB/wQEAwIHgDANBgkqhkiG9w0BAQsFAAOCAQEAkM+lfszmhNWVV17A0in4B3Uw3gOSOLbhMYlpRuJQXXwNQV44m5UE5xFChjB7mzYbRu2+aClTqoOdoz8C1VcYwkSfcr9y/3IyH2d0FJr+QIDWm32GRC7DMnl/cpGeJgw2/qoxyZ5xmodpneGdRGFc93nO1Bxp2oyB7WC3hBZtbufjE+Krt4uOhsun8/KtrCXX9JBmpD1GmP6rfml1bIguEW0Ga3jeNloDXk/KanHoJxlteNF6SJBN4BHfFsDzAWyOdzoU98MgEBbsoJradfM7Js5gZe3bXI3jt4vy97q73C9USbUvSCPUyf6klJDTo1IvyCjWE+VRbOrgG46L6PULNQ=="],"io.cncf.notary.signingAgent":"Notation/1.0.0"},"signature":"BgR1KCMwSty1SJo4w3zYj64BPZUU31UpMei1Yo_rkJRn2Iy1dTptEYqlqwR8D27ExWdojO8xtejkSG8LGcUi0cJL1b11nXOXgnkKZtuJivi6MADeuqrkr7xLCY5qwen4P1fzvWVW5N6kM2POG8VMuP9ydJYbpgKGeX_mN7baXT43wJXGJZBmrSJVENutmloBsnQqNFaHB0qDehdqE6xRj-HJgi1dJj31lSMarqOX-hOf7g52qDx_w50XzozucSyWppie-4x7XqLtLKLqTzC8SEh2CUwFnm0_dbZVVRUURspZPf5fsw7IVmQipI4Y46Ym8-5_60a_AxUI3p1ZN1FLKw"}b06e392c2d88206b492dbaf5381800578611eb24f86587bc7717cd3b736e5da7000066400000000000000000000013301466375446100412360ustar00rootroot00000000000000notation-1.2.0/test/e2e/testdata/registry/oci_layout/e2e-with-expired-cert/blobs/sha256{"schemaVersion":2,"mediaType":"application/vnd.oci.image.manifest.v1+json","config":{"mediaType":"application/vnd.cncf.notary.signature","digest":"sha256:44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a","size":2},"layers":[{"mediaType":"application/jose+json","digest":"sha256:8513a5ba473f32b78a87518b732700632c05cde0d59d179275c5c5a8096beeed","size":2154}],"subject":{"mediaType":"application/vnd.oci.image.manifest.v1+json","digest":"sha256:b8479de3f88fb259a0a9ea82a5b2a052a1ef3c4ebbcfc61482d5ae4c831f8af9","size":517},"annotations":{"io.cncf.notary.x509chain.thumbprint#S256":"[\"d9927f0d5c45c1bc4206b8c458306bb14b22d594d5086aaff71e1a1443a64632\"]","org.opencontainers.image.created":"2023-06-27T06:07:26Z"}}b8479de3f88fb259a0a9ea82a5b2a052a1ef3c4ebbcfc61482d5ae4c831f8af9000066400000000000000000000010051466375446100421620ustar00rootroot00000000000000notation-1.2.0/test/e2e/testdata/registry/oci_layout/e2e-with-expired-cert/blobs/sha256{"schemaVersion":2,"mediaType":"application/vnd.oci.image.manifest.v1+json","config":{"mediaType":"application/vnd.notation.config","digest":"sha256:44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a","size":2},"layers":[{"mediaType":"application/vnd.oci.image.layer.v1.tar","digest":"sha256:6de5fe1f75e4b1094c98086f403d5fd8117776ebe5aad1609281403d13dea070","size":17,"annotations":{"org.opencontainers.image.title":"e2e.txt"}}],"annotations":{"org.opencontainers.image.created":"2023-06-27T05:01:36Z"}}notation-1.2.0/test/e2e/testdata/registry/oci_layout/e2e-with-expired-cert/index.json000066400000000000000000000012401466375446100306630ustar00rootroot00000000000000{"schemaVersion":2,"manifests":[{"mediaType":"application/vnd.oci.image.manifest.v1+json","digest":"sha256:b8479de3f88fb259a0a9ea82a5b2a052a1ef3c4ebbcfc61482d5ae4c831f8af9","size":517,"annotations":{"org.opencontainers.image.ref.name":"v1"}},{"mediaType":"application/vnd.oci.image.manifest.v1+json","digest":"sha256:b06e392c2d88206b492dbaf5381800578611eb24f86587bc7717cd3b736e5da7","size":728},{"mediaType":"application/vnd.oci.image.index.v1+json","digest":"sha256:2396c911429d6ca56ad6c98143e38e319d90165aa43c2089c9078905253b0f6c","size":485,"annotations":{"org.opencontainers.image.ref.name":"sha256-b8479de3f88fb259a0a9ea82a5b2a052a1ef3c4ebbcfc61482d5ae4c831f8af9"}}]}notation-1.2.0/test/e2e/testdata/registry/oci_layout/e2e-with-expired-cert/oci-layout000066400000000000000000000000361466375446100306730ustar00rootroot00000000000000{"imageLayoutVersion":"1.0.0"}notation-1.2.0/test/e2e/testdata/registry/oci_layout/e2e/000077500000000000000000000000001466375446100233225ustar00rootroot00000000000000notation-1.2.0/test/e2e/testdata/registry/oci_layout/e2e/blobs/000077500000000000000000000000001466375446100244235ustar00rootroot00000000000000notation-1.2.0/test/e2e/testdata/registry/oci_layout/e2e/blobs/sha256/000077500000000000000000000000001466375446100254335ustar00rootroot0000000000000044136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a000066400000000000000000000000021466375446100361600ustar00rootroot00000000000000notation-1.2.0/test/e2e/testdata/registry/oci_layout/e2e/blobs/sha256{}6de5fe1f75e4b1094c98086f403d5fd8117776ebe5aad1609281403d13dea070000066400000000000000000000000211466375446100360130ustar00rootroot00000000000000notation-1.2.0/test/e2e/testdata/registry/oci_layout/e2e/blobs/sha256awesome notation b8479de3f88fb259a0a9ea82a5b2a052a1ef3c4ebbcfc61482d5ae4c831f8af9000066400000000000000000000010051466375446100366200ustar00rootroot00000000000000notation-1.2.0/test/e2e/testdata/registry/oci_layout/e2e/blobs/sha256{"schemaVersion":2,"mediaType":"application/vnd.oci.image.manifest.v1+json","config":{"mediaType":"application/vnd.notation.config","digest":"sha256:44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a","size":2},"layers":[{"mediaType":"application/vnd.oci.image.layer.v1.tar","digest":"sha256:6de5fe1f75e4b1094c98086f403d5fd8117776ebe5aad1609281403d13dea070","size":17,"annotations":{"org.opencontainers.image.title":"e2e.txt"}}],"annotations":{"org.opencontainers.image.created":"2023-06-27T05:01:36Z"}}notation-1.2.0/test/e2e/testdata/registry/oci_layout/e2e/index.json000066400000000000000000000003631466375446100253260ustar00rootroot00000000000000{"schemaVersion":2,"manifests":[{"mediaType":"application/vnd.oci.image.manifest.v1+json","digest":"sha256:b8479de3f88fb259a0a9ea82a5b2a052a1ef3c4ebbcfc61482d5ae4c831f8af9","size":517,"annotations":{"org.opencontainers.image.ref.name":"v1"}}]}notation-1.2.0/test/e2e/testdata/registry/oci_layout/e2e/oci-layout000066400000000000000000000000361466375446100253310ustar00rootroot00000000000000{"imageLayoutVersion":"1.0.0"}notation-1.2.0/test/e2e/testdata/registry/zot/000077500000000000000000000000001466375446100213145ustar00rootroot00000000000000notation-1.2.0/test/e2e/testdata/registry/zot/config.json000066400000000000000000000004571466375446100234620ustar00rootroot00000000000000{ "storage": { "rootDirectory": "/var/lib/registry" }, "http": { "address": "0.0.0.0", "port": "5000", "auth": { "htpasswd": { "path": "/etc/zot/htpasswd" } } }, "log": { "level": "debug" } }notation-1.2.0/test/e2e/testdata/registry/zot/htpasswd000066400000000000000000000001071466375446100230720ustar00rootroot00000000000000testuser:$2y$05$E8rldH.g4uS278rwECRHBOxkbrgdkthdMa9vHqOcS525QxRCgYrS2