Compare commits
6 Commits
Author | SHA1 | Date |
---|---|---|
|
e6dde2dc5f | |
|
f31b89695f | |
|
07b2508881 | |
|
a6ca9ee64a | |
|
7260694151 | |
|
a83c5be7ef |
|
@ -14,8 +14,5 @@
|
||||||
coverage:
|
coverage:
|
||||||
status:
|
status:
|
||||||
project:
|
project:
|
||||||
default:
|
|
||||||
target: 80%
|
|
||||||
patch:
|
|
||||||
default:
|
default:
|
||||||
target: 80%
|
target: 80%
|
|
@ -1,3 +1,3 @@
|
||||||
# Repo-Level Owners (in alphabetical order)
|
# Repo-Level Owners (in alphabetical order)
|
||||||
# Note: This is only for the notaryproject/notation-go repo
|
# Note: This is only for the notaryproject/notation-go repo
|
||||||
* @gokarnm @niazfk @priteshbandi @rgnote @shizhMSFT @toddysm @Two-Hearts @vaninrao10 @yizha1
|
* @gokarnm @JeyJeyGao @niazfk @priteshbandi @rgnote @shizhMSFT @toddysm @Two-Hearts @vaninrao10 @yizha1
|
||||||
|
|
|
@ -9,13 +9,11 @@ Yi Zha <yizha1@microsoft.com> (@yizha1)
|
||||||
|
|
||||||
# Repo-Level Maintainers (in alphabetical order)
|
# Repo-Level Maintainers (in alphabetical order)
|
||||||
# Note: This is for the notaryproject/notation-go repo
|
# Note: This is for the notaryproject/notation-go repo
|
||||||
|
Junjie Gao <junjiegao@microsoft.com> (@JeyJeyGao)
|
||||||
Milind Gokarn <gokarnm@amazon.com> (@gokarnm)
|
Milind Gokarn <gokarnm@amazon.com> (@gokarnm)
|
||||||
Patrick Zheng <patrickzheng@microsoft.com> (@Two-Hearts)
|
Patrick Zheng <patrickzheng@microsoft.com> (@Two-Hearts)
|
||||||
Rakesh Gariganti <garigant@amazon.com> (@rgnote)
|
Rakesh Gariganti <garigant@amazon.com> (@rgnote)
|
||||||
|
|
||||||
# Emeritus Org Maintainers (in alphabetical order)
|
# Emeritus Org Maintainers (in alphabetical order)
|
||||||
Justin Cormack <justin.cormack@docker.com> (@justincormack)
|
Justin Cormack <justin.cormack@docker.com> (@justincormack)
|
||||||
Steve Lasker <StevenLasker@hotmail.com> (@stevelasker)
|
Steve Lasker <StevenLasker@hotmail.com> (@stevelasker)
|
||||||
|
|
||||||
# Emeritus Repo-Level Maintainers (in alphabetical order)
|
|
||||||
Junjie Gao <junjiegao@microsoft.com> (@JeyJeyGao)
|
|
2
Makefile
2
Makefile
|
@ -29,7 +29,7 @@ clean:
|
||||||
.PHONY: check-line-endings
|
.PHONY: check-line-endings
|
||||||
check-line-endings: ## check line endings
|
check-line-endings: ## check line endings
|
||||||
! find . -name "*.go" -type f -exec file "{}" ";" | grep CRLF
|
! find . -name "*.go" -type f -exec file "{}" ";" | grep CRLF
|
||||||
! find . -name "*.sh" -type f -exec file "{}" ";" | grep CRLF
|
! find scripts -name "*.sh" -type f -exec file "{}" ";" | grep CRLF
|
||||||
|
|
||||||
.PHONY: fix-line-endings
|
.PHONY: fix-line-endings
|
||||||
fix-line-endings: ## fix line endings
|
fix-line-endings: ## fix line endings
|
||||||
|
|
|
@ -3,7 +3,6 @@
|
||||||
[](https://github.com/notaryproject/notation-go/actions/workflows/build.yml?query=workflow%3Abuild+event%3Apush+branch%3Amain)
|
[](https://github.com/notaryproject/notation-go/actions/workflows/build.yml?query=workflow%3Abuild+event%3Apush+branch%3Amain)
|
||||||
[](https://codecov.io/gh/notaryproject/notation-go)
|
[](https://codecov.io/gh/notaryproject/notation-go)
|
||||||
[](https://pkg.go.dev/github.com/notaryproject/notation-go@main)
|
[](https://pkg.go.dev/github.com/notaryproject/notation-go@main)
|
||||||
[](https://scorecard.dev/viewer/?uri=github.com/notaryproject/notation-go)
|
|
||||||
|
|
||||||
notation-go contains libraries for signing and verification of artifacts as per [Notary Project specifications](https://github.com/notaryproject/specifications). notation-go is being used by [notation](https://github.com/notaryproject/notation) CLI for signing and verifying artifacts.
|
notation-go contains libraries for signing and verification of artifacts as per [Notary Project specifications](https://github.com/notaryproject/specifications). notation-go is being used by [notation](https://github.com/notaryproject/notation) CLI for signing and verifying artifacts.
|
||||||
|
|
||||||
|
|
|
@ -1,36 +0,0 @@
|
||||||
# Release Checklist
|
|
||||||
|
|
||||||
## Overview
|
|
||||||
|
|
||||||
This document describes the checklist to publish a release for notation-go.
|
|
||||||
|
|
||||||
## Release Process from main
|
|
||||||
|
|
||||||
1. Check if there are any security vulnerabilities fixed and security advisories published before a release. Security advisories should be linked on the release notes.
|
|
||||||
2. Determine a [SemVer2](https://semver.org/)-valid version prefixed with the letter `v` for release. For example, `version="v1.0.0-rc.1"`.
|
|
||||||
3. If there is new release in [notation-core-go](https://github.com/notaryproject/notation-core-go) library that are required to be upgraded in notation-go, update the dependency versions in the follow `go.mod` and `go.sum` files of notation-go:
|
|
||||||
- [go.mod](go.mod), [go.sum](go.sum)
|
|
||||||
4. Open a bump up PR and submit the changes in step 3 to the notation-go repository.
|
|
||||||
5. After PR from step 4 is merged. Create another PR to update the value of `signingAgent` defined in file [signer/signer.go](signer/signer.go) with `notation-go/<version>`, where `<version>` is `$version` from step 2 without the `v` prefix. For example, `notation-go/1.0.0-rc.1`. The commit message MUST follow the [conventional commit](https://www.conventionalcommits.org/en/v1.0.0/) and could be `bump: release $version`. Record the digest of that commit as `<commit_digest>`. 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: release $version`. Make sure to reach a majority of approvals from the [repo-level maintainers](MAINTAINERS) before merging it. This PR MUST 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.
|
|
||||||
6. After the voting PR is merged, execute `git clone https://github.com/notaryproject/notation-go.git` to clone the repository to your local file system.
|
|
||||||
7. Enter the cloned repository and execute `git checkout <commit_digest>` to switch to the specified branch based on the voting result.
|
|
||||||
8. Create a tag by running `git tag -am $version $version -s`.
|
|
||||||
9. 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`.
|
|
||||||
10. On notation-go GitHub page, goto [Tags](https://github.com/notaryproject/notation-go/tags). Your newly pushed tag should be shown on the top. Create a new release from the tag. Generate the release notes, revise the release description and change logs, and publish the release.
|
|
||||||
11. Announce the new release in the Notary Project community.
|
|
||||||
|
|
||||||
## Release Process from a release branch
|
|
||||||
|
|
||||||
1. Check if there are any security vulnerabilities fixed and security advisories published before a release. Security advisories should be linked on the release notes.
|
|
||||||
2. Determine a [SemVer2](https://semver.org/)-valid version prefixed with the letter `v` for release. For example, `version="v1.2.0-rc.1"`.
|
|
||||||
3. If a new release branch is needed, from main branch's [commit list](https://github.com/notaryproject/notation-go/commits/main/), find the commit that you want to cut the release. Click `<>` (Browse repository at this point). Create branch with name `release-<version>` from the commit, where `<version>` is `$version` from step 2 with the major and minor versions only. For example `release-1.2`. If the release branch already exists, skip this step.
|
|
||||||
4. If there is new release in [notation-core-go](https://github.com/notaryproject/notation-core-go) library that are required to be upgraded in notation-go, update the dependency versions in the follow `go.mod` and `go.sum` files of notation-go:
|
|
||||||
- [go.mod](go.mod), [go.sum](go.sum)
|
|
||||||
5. Open a bump up PR and submit the changes in step 4 to the release branch.
|
|
||||||
6. After PR from step 5 is merged. Create another PR to update the value of `signingAgent` defined in file `signer/signer.go` with `notation-go/<version>`, where `<version>` is `$version` from step 2 without the `v` prefix. For example, `notation-go/1.2.0-rc.1`. The commit message MUST follow the [conventional commit](https://www.conventionalcommits.org/en/v1.0.0/) and could be `bump: release $version`. Record the digest of that commit as `<commit_digest>`. 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: release $version`. Make sure to reach a majority of approvals from the [repo-level maintainers](MAINTAINERS) before merging it. This PR MUST 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.
|
|
||||||
7. After the voting PR is merged, execute `git clone https://github.com/notaryproject/notation-go.git` to clone the repository to your local file system.
|
|
||||||
8. Enter the cloned repository and execute `git checkout <commit_digest>` to switch to the specified branch based on the voting result.
|
|
||||||
9. Create a tag by running `git tag -am $version $version -s`.
|
|
||||||
10. 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`.
|
|
||||||
11. On notation-go GitHub page, goto [Tags](https://github.com/notaryproject/notation-go/tags). Your newly pushed tag should be shown on the top. Create a new release from the tag. Generate the release notes, revise the release description and change logs, and publish the release.
|
|
||||||
12. Announce the new release in the Notary Project community.
|
|
|
@ -1,35 +0,0 @@
|
||||||
// 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 config
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ErrKeyNameEmpty is used when key name is empty.
|
|
||||||
var ErrKeyNameEmpty = errors.New("key name cannot be empty")
|
|
||||||
|
|
||||||
// KeyNotFoundError is used when key is not found in the signingkeys.json file.
|
|
||||||
type KeyNotFoundError struct {
|
|
||||||
KeyName string
|
|
||||||
}
|
|
||||||
|
|
||||||
// Error returns the error message.
|
|
||||||
func (e KeyNotFoundError) Error() string {
|
|
||||||
if e.KeyName != "" {
|
|
||||||
return fmt.Sprintf("signing key %s not found", e.KeyName)
|
|
||||||
}
|
|
||||||
return "signing key not found"
|
|
||||||
}
|
|
|
@ -1,28 +0,0 @@
|
||||||
// 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 config
|
|
||||||
|
|
||||||
import "testing"
|
|
||||||
|
|
||||||
func TestErrorKeyNotFound(t *testing.T) {
|
|
||||||
e := KeyNotFoundError{}
|
|
||||||
if e.Error() != "signing key not found" {
|
|
||||||
t.Fatalf("ErrorKeyNotFound.Error() = %v, want %v", e.Error(), "signing key not found")
|
|
||||||
}
|
|
||||||
|
|
||||||
e = KeyNotFoundError{KeyName: "testKey"}
|
|
||||||
if e.Error() != `signing key testKey not found` {
|
|
||||||
t.Fatalf("ErrorKeyNotFound.Error() = %v, want %v", e.Error(), "signing key testKey not found")
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -50,6 +50,9 @@ type KeySuite struct {
|
||||||
*ExternalKey
|
*ExternalKey
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var errorKeyNameEmpty = errors.New("key name cannot be empty")
|
||||||
|
var errKeyNotFound = errors.New("signing key not found")
|
||||||
|
|
||||||
// SigningKeys reflects the signingkeys.json file.
|
// SigningKeys reflects the signingkeys.json file.
|
||||||
type SigningKeys struct {
|
type SigningKeys struct {
|
||||||
Default *string `json:"default,omitempty"`
|
Default *string `json:"default,omitempty"`
|
||||||
|
@ -64,12 +67,13 @@ func NewSigningKeys() *SigningKeys {
|
||||||
// Add adds new signing key
|
// Add adds new signing key
|
||||||
func (s *SigningKeys) Add(name, keyPath, certPath string, markDefault bool) error {
|
func (s *SigningKeys) Add(name, keyPath, certPath string, markDefault bool) error {
|
||||||
if name == "" {
|
if name == "" {
|
||||||
return ErrKeyNameEmpty
|
return errorKeyNameEmpty
|
||||||
}
|
}
|
||||||
_, err := tls.LoadX509KeyPair(certPath, keyPath)
|
_, err := tls.LoadX509KeyPair(certPath, keyPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
ks := KeySuite{
|
ks := KeySuite{
|
||||||
Name: name,
|
Name: name,
|
||||||
X509KeyPair: &X509KeyPair{
|
X509KeyPair: &X509KeyPair{
|
||||||
|
@ -84,20 +88,25 @@ func (s *SigningKeys) Add(name, keyPath, certPath string, markDefault bool) erro
|
||||||
func (s *SigningKeys) AddPlugin(ctx context.Context, keyName, id, pluginName string, pluginConfig map[string]string, markDefault bool) error {
|
func (s *SigningKeys) AddPlugin(ctx context.Context, keyName, id, pluginName string, pluginConfig map[string]string, markDefault bool) error {
|
||||||
logger := log.GetLogger(ctx)
|
logger := log.GetLogger(ctx)
|
||||||
logger.Debugf("Adding key with name %v and plugin name %v", keyName, pluginName)
|
logger.Debugf("Adding key with name %v and plugin name %v", keyName, pluginName)
|
||||||
|
|
||||||
if keyName == "" {
|
if keyName == "" {
|
||||||
return ErrKeyNameEmpty
|
return errorKeyNameEmpty
|
||||||
}
|
}
|
||||||
|
|
||||||
if id == "" {
|
if id == "" {
|
||||||
return errors.New("missing key id")
|
return errors.New("missing key id")
|
||||||
}
|
}
|
||||||
|
|
||||||
if pluginName == "" {
|
if pluginName == "" {
|
||||||
return errors.New("plugin name cannot be empty")
|
return errors.New("plugin name cannot be empty")
|
||||||
}
|
}
|
||||||
|
|
||||||
mgr := plugin.NewCLIManager(dir.PluginFS())
|
mgr := plugin.NewCLIManager(dir.PluginFS())
|
||||||
_, err := mgr.Get(ctx, pluginName)
|
_, err := mgr.Get(ctx, pluginName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
ks := KeySuite{
|
ks := KeySuite{
|
||||||
Name: keyName,
|
Name: keyName,
|
||||||
ExternalKey: &ExternalKey{
|
ExternalKey: &ExternalKey{
|
||||||
|
@ -106,6 +115,7 @@ func (s *SigningKeys) AddPlugin(ctx context.Context, keyName, id, pluginName str
|
||||||
PluginConfig: pluginConfig,
|
PluginConfig: pluginConfig,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = s.add(ks, markDefault); err != nil {
|
if err = s.add(ks, markDefault); err != nil {
|
||||||
logger.Error("Failed to add key with error: %v", err)
|
logger.Error("Failed to add key with error: %v", err)
|
||||||
return err
|
return err
|
||||||
|
@ -117,12 +127,14 @@ func (s *SigningKeys) AddPlugin(ctx context.Context, keyName, id, pluginName str
|
||||||
// Get returns signing key for the given name
|
// Get returns signing key for the given name
|
||||||
func (s *SigningKeys) Get(keyName string) (KeySuite, error) {
|
func (s *SigningKeys) Get(keyName string) (KeySuite, error) {
|
||||||
if keyName == "" {
|
if keyName == "" {
|
||||||
return KeySuite{}, ErrKeyNameEmpty
|
return KeySuite{}, errorKeyNameEmpty
|
||||||
}
|
}
|
||||||
|
|
||||||
idx := slices.IndexIsser(s.Keys, keyName)
|
idx := slices.IndexIsser(s.Keys, keyName)
|
||||||
if idx < 0 {
|
if idx < 0 {
|
||||||
return KeySuite{}, KeyNotFoundError{KeyName: keyName}
|
return KeySuite{}, errKeyNotFound
|
||||||
}
|
}
|
||||||
|
|
||||||
return s.Keys[idx], nil
|
return s.Keys[idx], nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -132,6 +144,7 @@ func (s *SigningKeys) GetDefault() (KeySuite, error) {
|
||||||
return KeySuite{}, errors.New("default signing key not set." +
|
return KeySuite{}, errors.New("default signing key not set." +
|
||||||
" Please set default signing key or specify a key name")
|
" Please set default signing key or specify a key name")
|
||||||
}
|
}
|
||||||
|
|
||||||
return s.Get(*s.Default)
|
return s.Get(*s.Default)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -140,11 +153,12 @@ func (s *SigningKeys) Remove(keyName ...string) ([]string, error) {
|
||||||
var deletedNames []string
|
var deletedNames []string
|
||||||
for _, name := range keyName {
|
for _, name := range keyName {
|
||||||
if name == "" {
|
if name == "" {
|
||||||
return deletedNames, ErrKeyNameEmpty
|
return deletedNames, errorKeyNameEmpty
|
||||||
}
|
}
|
||||||
|
|
||||||
idx := slices.IndexIsser(s.Keys, name)
|
idx := slices.IndexIsser(s.Keys, name)
|
||||||
if idx < 0 {
|
if idx < 0 {
|
||||||
return deletedNames, KeyNotFoundError{KeyName: name}
|
return deletedNames, errors.New(name + ": not found")
|
||||||
}
|
}
|
||||||
s.Keys = slices.Delete(s.Keys, idx)
|
s.Keys = slices.Delete(s.Keys, idx)
|
||||||
deletedNames = append(deletedNames, name)
|
deletedNames = append(deletedNames, name)
|
||||||
|
@ -158,11 +172,13 @@ func (s *SigningKeys) Remove(keyName ...string) ([]string, error) {
|
||||||
// UpdateDefault updates default signing key
|
// UpdateDefault updates default signing key
|
||||||
func (s *SigningKeys) UpdateDefault(keyName string) error {
|
func (s *SigningKeys) UpdateDefault(keyName string) error {
|
||||||
if keyName == "" {
|
if keyName == "" {
|
||||||
return ErrKeyNameEmpty
|
return errorKeyNameEmpty
|
||||||
}
|
}
|
||||||
|
|
||||||
if !slices.ContainsIsser(s.Keys, keyName) {
|
if !slices.ContainsIsser(s.Keys, keyName) {
|
||||||
return KeyNotFoundError{KeyName: keyName}
|
return fmt.Errorf("key with name '%s' not found", keyName)
|
||||||
}
|
}
|
||||||
|
|
||||||
s.Default = &keyName
|
s.Default = &keyName
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -173,9 +189,11 @@ func (s *SigningKeys) Save() error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := validateKeys(s); err != nil {
|
if err := validateKeys(s); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return save(path, s)
|
return save(path, s)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -190,9 +208,11 @@ func LoadSigningKeys() (*SigningKeys, error) {
|
||||||
}
|
}
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := validateKeys(&config); err != nil {
|
if err := validateKeys(&config); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return &config, nil
|
return &config, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -204,9 +224,11 @@ func LoadExecSaveSigningKeys(fn func(keys *SigningKeys) error) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := fn(signingKeys); err != nil {
|
if err := fn(signingKeys); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return signingKeys.Save()
|
return signingKeys.Save()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -219,10 +241,12 @@ func (s *SigningKeys) add(key KeySuite, markDefault bool) error {
|
||||||
if slices.ContainsIsser(s.Keys, key.Name) {
|
if slices.ContainsIsser(s.Keys, key.Name) {
|
||||||
return fmt.Errorf("signing key with name %q already exists", key.Name)
|
return fmt.Errorf("signing key with name %q already exists", key.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
s.Keys = append(s.Keys, key)
|
s.Keys = append(s.Keys, key)
|
||||||
if markDefault {
|
if markDefault {
|
||||||
s.Default = &key.Name
|
s.Default = &key.Name
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -238,14 +262,17 @@ func validateKeys(config *SigningKeys) error {
|
||||||
}
|
}
|
||||||
uniqueKeyNames.Add(key.Name)
|
uniqueKeyNames.Add(key.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
if config.Default != nil {
|
if config.Default != nil {
|
||||||
defaultKey := *config.Default
|
defaultKey := *config.Default
|
||||||
if len(defaultKey) == 0 {
|
if len(defaultKey) == 0 {
|
||||||
return fmt.Errorf("malformed %s: default key name cannot be empty", dir.PathSigningKeys)
|
return fmt.Errorf("malformed %s: default key name cannot be empty", dir.PathSigningKeys)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !uniqueKeyNames.Contains(defaultKey) {
|
if !uniqueKeyNames.Contains(defaultKey) {
|
||||||
return fmt.Errorf("malformed %s: default key '%s' not found", dir.PathSigningKeys, defaultKey)
|
return fmt.Errorf("malformed %s: default key '%s' not found", dir.PathSigningKeys, defaultKey)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,7 +17,6 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"encoding/pem"
|
"encoding/pem"
|
||||||
"errors"
|
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
@ -311,22 +310,14 @@ func TestGet(t *testing.T) {
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("NonExistent", func(t *testing.T) {
|
t.Run("NonExistent", func(t *testing.T) {
|
||||||
_, err := sampleSigningKeysInfo.Get("nonExistent")
|
if _, err := sampleSigningKeysInfo.Get("nonExistent"); err == nil {
|
||||||
if err == nil {
|
|
||||||
t.Error("expected Get() to fail for nonExistent key name")
|
t.Error("expected Get() to fail for nonExistent key name")
|
||||||
}
|
}
|
||||||
if !errors.Is(err, KeyNotFoundError{KeyName: "nonExistent"}) {
|
|
||||||
t.Error("expected Get() to return ErrorKeyNotFound")
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("EmptyName", func(t *testing.T) {
|
t.Run("InvalidName", func(t *testing.T) {
|
||||||
_, err := sampleSigningKeysInfo.Get("")
|
if _, err := sampleSigningKeysInfo.Get(""); err == nil {
|
||||||
if err == nil {
|
t.Error("expected Get() to fail for invalid key name")
|
||||||
t.Error("expected Get() to fail for empty key name")
|
|
||||||
}
|
|
||||||
if !errors.Is(err, ErrKeyNameEmpty) {
|
|
||||||
t.Error("expected Get() to return ErrorKeyNameEmpty")
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -367,22 +358,14 @@ func TestUpdateDefault(t *testing.T) {
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("NonExistent", func(t *testing.T) {
|
t.Run("NonExistent", func(t *testing.T) {
|
||||||
err := sampleSigningKeysInfo.UpdateDefault("nonExistent")
|
if err := sampleSigningKeysInfo.UpdateDefault("nonExistent"); err == nil {
|
||||||
if err == nil {
|
|
||||||
t.Error("expected Get() to fail for nonExistent key name")
|
t.Error("expected Get() to fail for nonExistent key name")
|
||||||
}
|
}
|
||||||
if !errors.Is(err, KeyNotFoundError{KeyName: "nonExistent"}) {
|
|
||||||
t.Error("expected Get() to return ErrorKeyNotFound")
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("EmptyName", func(t *testing.T) {
|
t.Run("InvalidName", func(t *testing.T) {
|
||||||
err := sampleSigningKeysInfo.UpdateDefault("")
|
if err := sampleSigningKeysInfo.UpdateDefault(""); err == nil {
|
||||||
if err == nil {
|
t.Error("expected Get() to fail for invalid key name")
|
||||||
t.Error("expected Get() to fail for empty key name")
|
|
||||||
}
|
|
||||||
if !errors.Is(err, ErrKeyNameEmpty) {
|
|
||||||
t.Error("expected Get() to return ErrorKeyNameEmpty")
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -399,28 +382,21 @@ func TestRemove(t *testing.T) {
|
||||||
if _, err := testSigningKeysInfo.Get(testKeyName); err == nil {
|
if _, err := testSigningKeysInfo.Get(testKeyName); err == nil {
|
||||||
t.Error("Delete() filed to delete key")
|
t.Error("Delete() filed to delete key")
|
||||||
}
|
}
|
||||||
|
|
||||||
if keys[0] != testKeyName {
|
if keys[0] != testKeyName {
|
||||||
t.Error("Delete() deleted key name mismatch")
|
t.Error("Delete() deleted key name mismatch")
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("NonExistent", func(t *testing.T) {
|
t.Run("NonExistent", func(t *testing.T) {
|
||||||
_, err := testSigningKeysInfo.Remove("nonExistent")
|
if _, err := testSigningKeysInfo.Remove(testKeyName); err == nil {
|
||||||
if err == nil {
|
|
||||||
t.Error("expected Get() to fail for nonExistent key name")
|
t.Error("expected Get() to fail for nonExistent key name")
|
||||||
}
|
}
|
||||||
if !errors.Is(err, KeyNotFoundError{KeyName: "nonExistent"}) {
|
|
||||||
t.Error("expected Get() to return ErrorKeyNotFound")
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("EmptyName", func(t *testing.T) {
|
t.Run("InvalidName", func(t *testing.T) {
|
||||||
_, err := testSigningKeysInfo.Remove("")
|
if _, err := testSigningKeysInfo.Remove(""); err == nil {
|
||||||
if err == nil {
|
t.Error("expected Get() to fail for invalid key name")
|
||||||
t.Error("expected Get() to fail for empty key name")
|
|
||||||
}
|
|
||||||
if !errors.Is(err, ErrKeyNameEmpty) {
|
|
||||||
t.Error("expected Get() to return ErrorKeyNameEmpty")
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -58,10 +58,3 @@ func ConfigFS() SysFS {
|
||||||
func PluginFS() SysFS {
|
func PluginFS() SysFS {
|
||||||
return NewSysFS(filepath.Join(userLibexecDirPath(), PathPlugins))
|
return NewSysFS(filepath.Join(userLibexecDirPath(), PathPlugins))
|
||||||
}
|
}
|
||||||
|
|
||||||
// CacheFS is the cache SysFS.
|
|
||||||
//
|
|
||||||
// To get the root of crl file cache, use `CacheFS().SysFS(PathCRLCache)`.
|
|
||||||
func CacheFS() SysFS {
|
|
||||||
return NewSysFS(userCacheDirPath())
|
|
||||||
}
|
|
||||||
|
|
|
@ -71,14 +71,3 @@ func TestPluginFS(t *testing.T) {
|
||||||
t.Fatalf(`SysPath() failed. got: %q, want: %q`, path, filepath.Join(userLibexecDirPath(), PathPlugins, "plugin"))
|
t.Fatalf(`SysPath() failed. got: %q, want: %q`, path, filepath.Join(userLibexecDirPath(), PathPlugins, "plugin"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCRLFileCacheFS(t *testing.T) {
|
|
||||||
cacheFS := CacheFS()
|
|
||||||
path, err := cacheFS.SysPath(PathCRLCache)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("SysPath() failed. err = %v", err)
|
|
||||||
}
|
|
||||||
if path != filepath.Join(UserCacheDir, PathCRLCache) {
|
|
||||||
t.Fatalf(`SysPath() failed. got: %q, want: %q`, path, UserConfigDir)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
50
dir/path.go
50
dir/path.go
|
@ -12,7 +12,7 @@
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
// Package dir implements Notation directory structure.
|
// Package dir implements Notation directory structure.
|
||||||
// [directory spec]: https://notaryproject.dev/docs/user-guides/how-to/directory-structure/
|
// [directory spec]: https://github.com/notaryproject/notation/blob/main/specs/directory.md
|
||||||
//
|
//
|
||||||
// Example:
|
// Example:
|
||||||
//
|
//
|
||||||
|
@ -31,7 +31,7 @@
|
||||||
// - Set custom configurations directory:
|
// - Set custom configurations directory:
|
||||||
// dir.UserConfigDir = '/path/to/configurations/'
|
// dir.UserConfigDir = '/path/to/configurations/'
|
||||||
//
|
//
|
||||||
// Only user level directory is supported, and system level directory
|
// Only user level directory is supported for RC.1, and system level directory
|
||||||
// may be added later.
|
// may be added later.
|
||||||
package dir
|
package dir
|
||||||
|
|
||||||
|
@ -44,7 +44,6 @@ import (
|
||||||
var (
|
var (
|
||||||
UserConfigDir string // Absolute path of user level {NOTATION_CONFIG}
|
UserConfigDir string // Absolute path of user level {NOTATION_CONFIG}
|
||||||
UserLibexecDir string // Absolute path of user level {NOTATION_LIBEXEC}
|
UserLibexecDir string // Absolute path of user level {NOTATION_LIBEXEC}
|
||||||
UserCacheDir string // Absolute path of user level {NOTATION_CACHE}
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -58,15 +57,10 @@ const (
|
||||||
PathConfigFile = "config.json"
|
PathConfigFile = "config.json"
|
||||||
// PathSigningKeys is the signingkeys file relative path.
|
// PathSigningKeys is the signingkeys file relative path.
|
||||||
PathSigningKeys = "signingkeys.json"
|
PathSigningKeys = "signingkeys.json"
|
||||||
// PathTrustPolicy is the OCI trust policy file relative path.
|
// PathTrustPolicy is the trust policy file relative path.
|
||||||
//
|
|
||||||
// Deprecated: PathTrustPolicy exists for historical compatibility and should not be used.
|
|
||||||
// To get OCI trust policy path, use PathOCITrustPolicy.
|
|
||||||
PathTrustPolicy = "trustpolicy.json"
|
PathTrustPolicy = "trustpolicy.json"
|
||||||
// PathOCITrustPolicy is the OCI trust policy file relative path.
|
// PathPlugins is the plugins directory relative path.
|
||||||
PathOCITrustPolicy = "trustpolicy.oci.json"
|
PathPlugins = "plugins"
|
||||||
// PathBlobTrustPolicy is the Blob trust policy file relative path.
|
|
||||||
PathBlobTrustPolicy = "trustpolicy.blob.json"
|
|
||||||
// LocalKeysDir is the directory name for local key relative path.
|
// LocalKeysDir is the directory name for local key relative path.
|
||||||
LocalKeysDir = "localkeys"
|
LocalKeysDir = "localkeys"
|
||||||
// LocalCertificateExtension defines the extension of the certificate files.
|
// LocalCertificateExtension defines the extension of the certificate files.
|
||||||
|
@ -77,24 +71,7 @@ const (
|
||||||
TrustStoreDir = "truststore"
|
TrustStoreDir = "truststore"
|
||||||
)
|
)
|
||||||
|
|
||||||
// The relative path to {NOTATION_LIBEXEC}
|
var userConfigDir = os.UserConfigDir // for unit test
|
||||||
const (
|
|
||||||
// PathPlugins is the plugins directory relative path.
|
|
||||||
PathPlugins = "plugins"
|
|
||||||
)
|
|
||||||
|
|
||||||
// The relative path to {NOTATION_CACHE}
|
|
||||||
const (
|
|
||||||
// PathCRLCache is the crl file cache directory relative path.
|
|
||||||
PathCRLCache = "crl"
|
|
||||||
)
|
|
||||||
|
|
||||||
// for unit tests
|
|
||||||
var (
|
|
||||||
userConfigDir = os.UserConfigDir
|
|
||||||
|
|
||||||
userCacheDir = os.UserCacheDir
|
|
||||||
)
|
|
||||||
|
|
||||||
// userConfigDirPath returns the user level {NOTATION_CONFIG} path.
|
// userConfigDirPath returns the user level {NOTATION_CONFIG} path.
|
||||||
func userConfigDirPath() string {
|
func userConfigDirPath() string {
|
||||||
|
@ -120,21 +97,6 @@ func userLibexecDirPath() string {
|
||||||
return UserLibexecDir
|
return UserLibexecDir
|
||||||
}
|
}
|
||||||
|
|
||||||
// userCacheDirPath returns the user level {NOTATION_CACHE} path.
|
|
||||||
func userCacheDirPath() string {
|
|
||||||
if UserCacheDir == "" {
|
|
||||||
userDir, err := userCacheDir()
|
|
||||||
if err != nil {
|
|
||||||
// fallback to current directory
|
|
||||||
UserCacheDir = filepath.Join("."+notation, "cache")
|
|
||||||
return UserCacheDir
|
|
||||||
}
|
|
||||||
// set user cache
|
|
||||||
UserCacheDir = filepath.Join(userDir, notation)
|
|
||||||
}
|
|
||||||
return UserCacheDir
|
|
||||||
}
|
|
||||||
|
|
||||||
// LocalKeyPath returns the local key and local cert relative paths.
|
// LocalKeyPath returns the local key and local cert relative paths.
|
||||||
func LocalKeyPath(name string) (keyPath, certPath string) {
|
func LocalKeyPath(name string) (keyPath, certPath string) {
|
||||||
basePath := path.Join(LocalKeysDir, name)
|
basePath := path.Join(LocalKeysDir, name)
|
||||||
|
|
|
@ -15,22 +15,20 @@ package dir
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
func mockUserPath() (string, error) {
|
func mockGetUserConfig() (string, error) {
|
||||||
return "/path/", nil
|
return "/path/", nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func setup() {
|
func setup() {
|
||||||
UserConfigDir = ""
|
UserConfigDir = ""
|
||||||
UserLibexecDir = ""
|
UserLibexecDir = ""
|
||||||
UserCacheDir = ""
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func Test_UserConfigDirPath(t *testing.T) {
|
func Test_UserConfigDirPath(t *testing.T) {
|
||||||
userConfigDir = mockUserPath
|
userConfigDir = mockGetUserConfig
|
||||||
setup()
|
setup()
|
||||||
got := userConfigDirPath()
|
got := userConfigDirPath()
|
||||||
if got != "/path/notation" {
|
if got != "/path/notation" {
|
||||||
|
@ -41,22 +39,16 @@ func Test_UserConfigDirPath(t *testing.T) {
|
||||||
func Test_NoHomeVariable(t *testing.T) {
|
func Test_NoHomeVariable(t *testing.T) {
|
||||||
t.Setenv("HOME", "")
|
t.Setenv("HOME", "")
|
||||||
t.Setenv("XDG_CONFIG_HOME", "")
|
t.Setenv("XDG_CONFIG_HOME", "")
|
||||||
t.Setenv("XDG_CACHE_HOME", "")
|
|
||||||
setup()
|
setup()
|
||||||
userConfigDir = os.UserConfigDir
|
userConfigDir = os.UserConfigDir
|
||||||
got := userConfigDirPath()
|
got := userConfigDirPath()
|
||||||
if got != ".notation" {
|
if got != ".notation" {
|
||||||
t.Fatalf(`userConfigDirPath() = %q, want ".notation"`, got)
|
t.Fatalf(`UserConfigDirPath() = %q, want ".notation"`, UserConfigDir)
|
||||||
}
|
|
||||||
got = userCacheDirPath()
|
|
||||||
want := filepath.Join("."+notation, "cache")
|
|
||||||
if got != want {
|
|
||||||
t.Fatalf(`userCacheDirPath() = %q, want %q`, got, want)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func Test_UserLibexecDirPath(t *testing.T) {
|
func Test_UserLibexecDirPath(t *testing.T) {
|
||||||
userConfigDir = mockUserPath
|
userConfigDir = mockGetUserConfig
|
||||||
setup()
|
setup()
|
||||||
got := userLibexecDirPath()
|
got := userLibexecDirPath()
|
||||||
if got != "/path/notation" {
|
if got != "/path/notation" {
|
||||||
|
@ -64,17 +56,8 @@ func Test_UserLibexecDirPath(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func Test_UserCacheDirPath(t *testing.T) {
|
|
||||||
userCacheDir = mockUserPath
|
|
||||||
setup()
|
|
||||||
got := userCacheDirPath()
|
|
||||||
if got != "/path/notation" {
|
|
||||||
t.Fatalf(`UserCacheDirPath() = %q, want "/path/notation"`, got)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestLocalKeyPath(t *testing.T) {
|
func TestLocalKeyPath(t *testing.T) {
|
||||||
userConfigDir = mockUserPath
|
userConfigDir = mockGetUserConfig
|
||||||
setup()
|
setup()
|
||||||
_ = userConfigDirPath()
|
_ = userConfigDirPath()
|
||||||
_ = userLibexecDirPath()
|
_ = userLibexecDirPath()
|
||||||
|
@ -88,7 +71,7 @@ func TestLocalKeyPath(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestX509TrustStoreDir(t *testing.T) {
|
func TestX509TrustStoreDir(t *testing.T) {
|
||||||
userConfigDir = mockUserPath
|
userConfigDir = mockGetUserConfig
|
||||||
setup()
|
setup()
|
||||||
_ = userConfigDirPath()
|
_ = userConfigDirPath()
|
||||||
_ = userLibexecDirPath()
|
_ = userLibexecDirPath()
|
||||||
|
|
60
errors.go
60
errors.go
|
@ -15,17 +15,11 @@ package notation
|
||||||
|
|
||||||
// ErrorPushSignatureFailed is used when failed to push signature to the
|
// ErrorPushSignatureFailed is used when failed to push signature to the
|
||||||
// target registry.
|
// target registry.
|
||||||
//
|
type ErrorPushSignatureFailed struct {
|
||||||
// Deprecated: Use PushSignatureFailedError instead.
|
|
||||||
type ErrorPushSignatureFailed = PushSignatureFailedError
|
|
||||||
|
|
||||||
// PushSignatureFailedError is used when failed to push signature to the
|
|
||||||
// target registry.
|
|
||||||
type PushSignatureFailedError struct {
|
|
||||||
Msg string
|
Msg string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e PushSignatureFailedError) Error() string {
|
func (e ErrorPushSignatureFailed) Error() string {
|
||||||
if e.Msg != "" {
|
if e.Msg != "" {
|
||||||
return "failed to push signature to registry with error: " + e.Msg
|
return "failed to push signature to registry with error: " + e.Msg
|
||||||
}
|
}
|
||||||
|
@ -34,17 +28,11 @@ func (e PushSignatureFailedError) Error() string {
|
||||||
|
|
||||||
// ErrorVerificationInconclusive is used when signature verification fails due
|
// ErrorVerificationInconclusive is used when signature verification fails due
|
||||||
// to a runtime error (e.g. a network error)
|
// to a runtime error (e.g. a network error)
|
||||||
//
|
type ErrorVerificationInconclusive struct {
|
||||||
// Deprecated: Use VerificationInconclusiveError instead.
|
|
||||||
type ErrorVerificationInconclusive = VerificationInconclusiveError
|
|
||||||
|
|
||||||
// VerificationInconclusiveError is used when signature verification fails due
|
|
||||||
// to a runtime error (e.g. a network error)
|
|
||||||
type VerificationInconclusiveError struct {
|
|
||||||
Msg string
|
Msg string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e VerificationInconclusiveError) Error() string {
|
func (e ErrorVerificationInconclusive) Error() string {
|
||||||
if e.Msg != "" {
|
if e.Msg != "" {
|
||||||
return e.Msg
|
return e.Msg
|
||||||
}
|
}
|
||||||
|
@ -53,17 +41,11 @@ func (e VerificationInconclusiveError) Error() string {
|
||||||
|
|
||||||
// ErrorNoApplicableTrustPolicy is used when there is no trust policy that
|
// ErrorNoApplicableTrustPolicy is used when there is no trust policy that
|
||||||
// applies to the given artifact
|
// applies to the given artifact
|
||||||
//
|
type ErrorNoApplicableTrustPolicy struct {
|
||||||
// Deprecated: Use NoApplicableTrustPolicyError instead.
|
|
||||||
type ErrorNoApplicableTrustPolicy = NoApplicableTrustPolicyError
|
|
||||||
|
|
||||||
// NoApplicableTrustPolicyError is used when there is no trust policy that
|
|
||||||
// applies to the given artifact
|
|
||||||
type NoApplicableTrustPolicyError struct {
|
|
||||||
Msg string
|
Msg string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e NoApplicableTrustPolicyError) Error() string {
|
func (e ErrorNoApplicableTrustPolicy) Error() string {
|
||||||
if e.Msg != "" {
|
if e.Msg != "" {
|
||||||
return e.Msg
|
return e.Msg
|
||||||
}
|
}
|
||||||
|
@ -72,17 +54,11 @@ func (e NoApplicableTrustPolicyError) Error() string {
|
||||||
|
|
||||||
// ErrorSignatureRetrievalFailed is used when notation is unable to retrieve the
|
// ErrorSignatureRetrievalFailed is used when notation is unable to retrieve the
|
||||||
// digital signature/s for the given artifact
|
// digital signature/s for the given artifact
|
||||||
//
|
type ErrorSignatureRetrievalFailed struct {
|
||||||
// Deprecated: Use SignatureRetrievalFailedError instead.
|
|
||||||
type ErrorSignatureRetrievalFailed = SignatureRetrievalFailedError
|
|
||||||
|
|
||||||
// SignatureRetrievalFailedError is used when notation is unable to retrieve the
|
|
||||||
// digital signature/s for the given artifact
|
|
||||||
type SignatureRetrievalFailedError struct {
|
|
||||||
Msg string
|
Msg string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e SignatureRetrievalFailedError) Error() string {
|
func (e ErrorSignatureRetrievalFailed) Error() string {
|
||||||
if e.Msg != "" {
|
if e.Msg != "" {
|
||||||
return e.Msg
|
return e.Msg
|
||||||
}
|
}
|
||||||
|
@ -91,17 +67,11 @@ func (e SignatureRetrievalFailedError) Error() string {
|
||||||
|
|
||||||
// ErrorVerificationFailed is used when it is determined that the digital
|
// ErrorVerificationFailed is used when it is determined that the digital
|
||||||
// signature/s is not valid for the given artifact
|
// signature/s is not valid for the given artifact
|
||||||
//
|
type ErrorVerificationFailed struct {
|
||||||
// Deprecated: Use VerificationFailedError instead.
|
|
||||||
type ErrorVerificationFailed = VerificationFailedError
|
|
||||||
|
|
||||||
// VerificationFailedError is used when it is determined that the digital
|
|
||||||
// signature/s is not valid for the given artifact
|
|
||||||
type VerificationFailedError struct {
|
|
||||||
Msg string
|
Msg string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e VerificationFailedError) Error() string {
|
func (e ErrorVerificationFailed) Error() string {
|
||||||
if e.Msg != "" {
|
if e.Msg != "" {
|
||||||
return e.Msg
|
return e.Msg
|
||||||
}
|
}
|
||||||
|
@ -110,17 +80,11 @@ func (e VerificationFailedError) Error() string {
|
||||||
|
|
||||||
// ErrorUserMetadataVerificationFailed is used when the signature does not
|
// ErrorUserMetadataVerificationFailed is used when the signature does not
|
||||||
// contain the user specified metadata
|
// contain the user specified metadata
|
||||||
//
|
type ErrorUserMetadataVerificationFailed struct {
|
||||||
// Deprecated: Use UserMetadataVerificationFailedError instead.
|
|
||||||
type ErrorUserMetadataVerificationFailed = UserMetadataVerificationFailedError
|
|
||||||
|
|
||||||
// UserMetadataVerificationFailedError is used when the signature does not
|
|
||||||
// contain the user specified metadata
|
|
||||||
type UserMetadataVerificationFailedError struct {
|
|
||||||
Msg string
|
Msg string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e UserMetadataVerificationFailedError) Error() string {
|
func (e ErrorUserMetadataVerificationFailed) Error() string {
|
||||||
if e.Msg != "" {
|
if e.Msg != "" {
|
||||||
return e.Msg
|
return e.Msg
|
||||||
}
|
}
|
||||||
|
|
|
@ -91,80 +91,3 @@ func TestErrorMessages(t *testing.T) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCustomErrorPrintsCorrectMessage(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
err error
|
|
||||||
want string
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "PushSignatureFailedError with message",
|
|
||||||
err: PushSignatureFailedError{Msg: "test message"},
|
|
||||||
want: "failed to push signature to registry with error: test message",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "PushSignatureFailedError without message",
|
|
||||||
err: PushSignatureFailedError{},
|
|
||||||
want: "failed to push signature to registry",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "VerificationInconclusiveError with message",
|
|
||||||
err: VerificationInconclusiveError{Msg: "test message"},
|
|
||||||
want: "test message",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "VerificationInconclusiveError without message",
|
|
||||||
err: VerificationInconclusiveError{},
|
|
||||||
want: "signature verification was inclusive due to an unexpected error",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "NoApplicableTrustPolicyError with message",
|
|
||||||
err: NoApplicableTrustPolicyError{Msg: "test message"},
|
|
||||||
want: "test message",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "NoApplicableTrustPolicyError without message",
|
|
||||||
err: NoApplicableTrustPolicyError{},
|
|
||||||
want: "there is no applicable trust policy for the given artifact",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "SignatureRetrievalFailedError with message",
|
|
||||||
err: SignatureRetrievalFailedError{Msg: "test message"},
|
|
||||||
want: "test message",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "SignatureRetrievalFailedError without message",
|
|
||||||
err: SignatureRetrievalFailedError{},
|
|
||||||
want: "unable to retrieve the digital signature from the registry",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "VerificationFailedError with message",
|
|
||||||
err: VerificationFailedError{Msg: "test message"},
|
|
||||||
want: "test message",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "VerificationFailedError without message",
|
|
||||||
err: VerificationFailedError{},
|
|
||||||
want: "signature verification failed",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "UserMetadataVerificationFailedError with message",
|
|
||||||
err: UserMetadataVerificationFailedError{Msg: "test message"},
|
|
||||||
want: "test message",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "UserMetadataVerificationFailedError without message",
|
|
||||||
err: UserMetadataVerificationFailedError{},
|
|
||||||
want: "unable to find specified metadata in the signature",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
if got := tt.err.Error(); got != tt.want {
|
|
||||||
t.Errorf("Error() = %v, want %v", got, tt.want)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -46,7 +46,7 @@ func Example_localSign() {
|
||||||
// exampleSigner is a notation.Signer given key and X509 certificate chain.
|
// exampleSigner is a notation.Signer given key and X509 certificate chain.
|
||||||
// Users should replace `exampleCertTuple.PrivateKey` with their own private
|
// Users should replace `exampleCertTuple.PrivateKey` with their own private
|
||||||
// key and replace `exampleCerts` with the corresponding full certificate
|
// key and replace `exampleCerts` with the corresponding full certificate
|
||||||
// chain, following the Notary Project certificate requirements:
|
// chain, following the Notary certificate requirements:
|
||||||
// https://github.com/notaryproject/notaryproject/blob/v1.0.0/specs/signature-specification.md#certificate-requirements
|
// https://github.com/notaryproject/notaryproject/blob/v1.0.0/specs/signature-specification.md#certificate-requirements
|
||||||
exampleSigner, err := signer.NewGenericSigner(exampleCertTuple.PrivateKey, exampleCerts)
|
exampleSigner, err := signer.NewGenericSigner(exampleCertTuple.PrivateKey, exampleCerts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -32,9 +32,9 @@ import (
|
||||||
// examplePolicyDocument is an example of a valid trust policy document.
|
// examplePolicyDocument is an example of a valid trust policy document.
|
||||||
// trust policy document should follow this spec:
|
// trust policy document should follow this spec:
|
||||||
// https://github.com/notaryproject/notaryproject/blob/v1.0.0/specs/trust-store-trust-policy.md#trust-policy
|
// https://github.com/notaryproject/notaryproject/blob/v1.0.0/specs/trust-store-trust-policy.md#trust-policy
|
||||||
var examplePolicyDocument = trustpolicy.OCIDocument{
|
var examplePolicyDocument = trustpolicy.Document{
|
||||||
Version: "1.0",
|
Version: "1.0",
|
||||||
TrustPolicies: []trustpolicy.OCITrustPolicy{
|
TrustPolicies: []trustpolicy.TrustPolicy{
|
||||||
{
|
{
|
||||||
Name: "test-statement-name",
|
Name: "test-statement-name",
|
||||||
RegistryScopes: []string{"example/software"},
|
RegistryScopes: []string{"example/software"},
|
||||||
|
@ -73,8 +73,8 @@ func Example_localVerify() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// createTrustStore creates a trust store directory for demo purpose.
|
// createTrustStore creates a trust store directory for demo purpose.
|
||||||
// Users could use the default trust store from Notary Project and
|
// Users could use the default trust store from Notary and add trusted
|
||||||
// add trusted certificates into it following the trust store spec:
|
// certificates into it following the trust store spec:
|
||||||
// https://github.com/notaryproject/notaryproject/blob/v1.0.0/specs/trust-store-trust-policy.md#trust-store
|
// https://github.com/notaryproject/notaryproject/blob/v1.0.0/specs/trust-store-trust-policy.md#trust-store
|
||||||
if err := createTrustStore(); err != nil {
|
if err := createTrustStore(); err != nil {
|
||||||
panic(err) // Handle error
|
panic(err) // Handle error
|
||||||
|
@ -172,7 +172,7 @@ func createTrustStore() error {
|
||||||
// generate the `exampleSignatureEnvelopePem` above.)
|
// generate the `exampleSignatureEnvelopePem` above.)
|
||||||
// Users should replace `exampleX509Certificate` with their own trusted
|
// Users should replace `exampleX509Certificate` with their own trusted
|
||||||
// certificate and add to the trust store, following the
|
// certificate and add to the trust store, following the
|
||||||
// Notary Project certificate requirements:
|
// Notary certificate requirements:
|
||||||
// https://github.com/notaryproject/notaryproject/blob/v1.0.0/specs/signature-specification.md#certificate-requirements
|
// https://github.com/notaryproject/notaryproject/blob/v1.0.0/specs/signature-specification.md#certificate-requirements
|
||||||
exampleX509Certificate := `-----BEGIN CERTIFICATE-----
|
exampleX509Certificate := `-----BEGIN CERTIFICATE-----
|
||||||
MIIDQDCCAiigAwIBAgIBUTANBgkqhkiG9w0BAQsFADBOMQswCQYDVQQGEwJVUzEL
|
MIIDQDCCAiigAwIBAgIBUTANBgkqhkiG9w0BAQsFADBOMQswCQYDVQQGEwJVUzEL
|
||||||
|
|
|
@ -45,7 +45,7 @@ func Example_remoteSign() {
|
||||||
// exampleSigner is a notation.Signer given key and X509 certificate chain.
|
// exampleSigner is a notation.Signer given key and X509 certificate chain.
|
||||||
// Users should replace `exampleCertTuple.PrivateKey` with their own private
|
// Users should replace `exampleCertTuple.PrivateKey` with their own private
|
||||||
// key and replace `exampleCerts` with the corresponding full certificate
|
// key and replace `exampleCerts` with the corresponding full certificate
|
||||||
// chain, following the Notary Project certificate requirements:
|
// chain, following the Notary certificate requirements:
|
||||||
// https://github.com/notaryproject/notaryproject/blob/v1.0.0/specs/signature-specification.md#certificate-requirements
|
// https://github.com/notaryproject/notaryproject/blob/v1.0.0/specs/signature-specification.md#certificate-requirements
|
||||||
exampleSigner, err := signer.NewGenericSigner(exampleCertTuple.PrivateKey, exampleCerts)
|
exampleSigner, err := signer.NewGenericSigner(exampleCertTuple.PrivateKey, exampleCerts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -70,16 +70,13 @@ func Example_remoteSign() {
|
||||||
// remote sign core process
|
// remote sign core process
|
||||||
// upon successful signing, descriptor of the sign content is returned and
|
// upon successful signing, descriptor of the sign content is returned and
|
||||||
// the generated signature is pushed into remote registry.
|
// the generated signature is pushed into remote registry.
|
||||||
targetManifestDesc, sigManifestDesc, err := notation.SignOCI(context.Background(), exampleSigner, exampleRepo, exampleSignOptions)
|
targetDesc, err := notation.Sign(context.Background(), exampleSigner, exampleRepo, exampleSignOptions)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err) // Handle error
|
panic(err) // Handle error
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Println("Successfully signed")
|
fmt.Println("Successfully signed")
|
||||||
fmt.Println("targetManifestDesc.MediaType:", targetManifestDesc.MediaType)
|
fmt.Println("targetDesc MediaType:", targetDesc.MediaType)
|
||||||
fmt.Println("targetManifestDesc.Digest:", targetManifestDesc.Digest)
|
fmt.Println("targetDesc Digest:", targetDesc.Digest)
|
||||||
fmt.Println("targetManifestDesc.Size:", targetManifestDesc.Size)
|
fmt.Println("targetDesc Size:", targetDesc.Size)
|
||||||
fmt.Println("sigManifestDesc.MediaType:", sigManifestDesc.MediaType)
|
|
||||||
fmt.Println("sigManifestDesc.Digest:", sigManifestDesc.Digest)
|
|
||||||
fmt.Println("sigManifestDesc.Size:", sigManifestDesc.Size)
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,9 +38,9 @@ func Example_remoteVerify() {
|
||||||
// examplePolicyDocument is an example of a valid trust policy document.
|
// examplePolicyDocument is an example of a valid trust policy document.
|
||||||
// trust policy document should follow this spec:
|
// trust policy document should follow this spec:
|
||||||
// https://github.com/notaryproject/notaryproject/blob/v1.0.0/specs/trust-store-trust-policy.md#trust-policy
|
// https://github.com/notaryproject/notaryproject/blob/v1.0.0/specs/trust-store-trust-policy.md#trust-policy
|
||||||
examplePolicyDocument := trustpolicy.OCIDocument{
|
examplePolicyDocument := trustpolicy.Document{
|
||||||
Version: "1.0",
|
Version: "1.0",
|
||||||
TrustPolicies: []trustpolicy.OCITrustPolicy{
|
TrustPolicies: []trustpolicy.TrustPolicy{
|
||||||
{
|
{
|
||||||
Name: "test-statement-name",
|
Name: "test-statement-name",
|
||||||
RegistryScopes: []string{"*"},
|
RegistryScopes: []string{"*"},
|
||||||
|
@ -101,7 +101,7 @@ func generateTrustStore() error {
|
||||||
// an example of a valid X509 self-signed certificate for demo purpose ONLY.
|
// an example of a valid X509 self-signed certificate for demo purpose ONLY.
|
||||||
// Users should replace `exampleX509Certificate` with their own trusted
|
// Users should replace `exampleX509Certificate` with their own trusted
|
||||||
// certificate and add to the trust store, following the
|
// certificate and add to the trust store, following the
|
||||||
// Notary Project certificate requirements:
|
// Notary certificate requirements:
|
||||||
// https://github.com/notaryproject/notaryproject/blob/v1.0.0/specs/signature-specification.md#certificate-requirements
|
// https://github.com/notaryproject/notaryproject/blob/v1.0.0/specs/signature-specification.md#certificate-requirements
|
||||||
exampleX509Certificate := `-----BEGIN CERTIFICATE-----
|
exampleX509Certificate := `-----BEGIN CERTIFICATE-----
|
||||||
MIIDQDCCAiigAwIBAgIBUTANBgkqhkiG9w0BAQsFADBOMQswCQYDVQQGEwJVUzEL
|
MIIDQDCCAiigAwIBAgIBUTANBgkqhkiG9w0BAQsFADBOMQswCQYDVQQGEwJVUzEL
|
||||||
|
|
|
@ -1,88 +0,0 @@
|
||||||
// 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_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/notaryproject/notation-core-go/signature"
|
|
||||||
"github.com/notaryproject/notation-core-go/signature/jws"
|
|
||||||
"github.com/notaryproject/notation-go"
|
|
||||||
"github.com/notaryproject/notation-go/signer"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ExampleSignBlob demonstrates how to use [notation.SignBlob] to sign arbitrary
|
|
||||||
// data.
|
|
||||||
func Example_signBlob() {
|
|
||||||
// exampleSigner implements [notation.Signer] and [notation.BlobSigner].
|
|
||||||
// Given key and X509 certificate chain, it provides method to sign OCI
|
|
||||||
// artifacts or blobs.
|
|
||||||
// Users should replace `exampleCertTuple.PrivateKey` with their own private
|
|
||||||
// key and replace `exampleCerts` with the corresponding certificate chain,
|
|
||||||
// following the Notary Project certificate requirements:
|
|
||||||
// https://github.com/notaryproject/specifications/tree/9c81dc773508dedc5a81c02c8d805de04f65050b/specs/signature-specification.md#certificate-requirements
|
|
||||||
exampleSigner, err := signer.NewGenericSigner(exampleCertTuple.PrivateKey, exampleCerts)
|
|
||||||
if err != nil {
|
|
||||||
panic(err) // Handle error
|
|
||||||
}
|
|
||||||
|
|
||||||
// Both COSE ("application/cose") and JWS ("application/jose+json")
|
|
||||||
// signature mediaTypes are supported.
|
|
||||||
exampleSignatureMediaType := jws.MediaTypeEnvelope
|
|
||||||
exampleContentMediaType := "video/mp4"
|
|
||||||
|
|
||||||
// exampleSignOptions is an example of [notation.SignBlobOptions].
|
|
||||||
exampleSignOptions := notation.SignBlobOptions{
|
|
||||||
SignerSignOptions: notation.SignerSignOptions{
|
|
||||||
SignatureMediaType: exampleSignatureMediaType,
|
|
||||||
SigningAgent: "example signing agent",
|
|
||||||
},
|
|
||||||
ContentMediaType: exampleContentMediaType,
|
|
||||||
UserMetadata: map[string]string{"buildId": "101"},
|
|
||||||
}
|
|
||||||
|
|
||||||
// exampleReader reads the data that needs to be signed.
|
|
||||||
// This data can be in a file or in memory.
|
|
||||||
exampleReader := strings.NewReader("example blob")
|
|
||||||
|
|
||||||
// Upon successful signing, signature envelope and signerInfo are returned.
|
|
||||||
// signatureEnvelope can be used in a verification process later on.
|
|
||||||
signatureEnvelope, signerInfo, err := notation.SignBlob(context.Background(), exampleSigner, exampleReader, exampleSignOptions)
|
|
||||||
if err != nil {
|
|
||||||
panic(err) // Handle error
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Println("Successfully signed")
|
|
||||||
|
|
||||||
// a peek of the signature envelope generated
|
|
||||||
sigBlob, err := signature.ParseEnvelope(exampleSignatureMediaType, signatureEnvelope)
|
|
||||||
if err != nil {
|
|
||||||
panic(err) // Handle error
|
|
||||||
}
|
|
||||||
sigContent, err := sigBlob.Content()
|
|
||||||
if err != nil {
|
|
||||||
panic(err) // Handle error
|
|
||||||
}
|
|
||||||
fmt.Println("signature Payload ContentType:", sigContent.Payload.ContentType)
|
|
||||||
fmt.Println("signature Payload Content:", string(sigContent.Payload.Content))
|
|
||||||
fmt.Println("signerInfo SigningAgent:", signerInfo.UnsignedAttributes.SigningAgent)
|
|
||||||
|
|
||||||
// Output:
|
|
||||||
// Successfully signed
|
|
||||||
// signature Payload ContentType: application/vnd.cncf.notary.payload.v1+json
|
|
||||||
// signature Payload Content: {"targetArtifact":{"annotations":{"buildId":"101"},"digest":"sha384:b8ab24dafba5cf7e4c89c562f811cf10493d4203da982d3b1345f366ca863d9c2ed323dbd0fb7ff83a80302ceffa5a61","mediaType":"video/mp4","size":12}}
|
|
||||||
// signerInfo SigningAgent: example signing agent
|
|
||||||
}
|
|
|
@ -21,8 +21,6 @@ import (
|
||||||
|
|
||||||
"oras.land/oras-go/v2/registry/remote"
|
"oras.land/oras-go/v2/registry/remote"
|
||||||
|
|
||||||
"github.com/notaryproject/notation-core-go/revocation"
|
|
||||||
"github.com/notaryproject/notation-core-go/revocation/purpose"
|
|
||||||
"github.com/notaryproject/notation-core-go/testhelper"
|
"github.com/notaryproject/notation-core-go/testhelper"
|
||||||
"github.com/notaryproject/notation-go"
|
"github.com/notaryproject/notation-go"
|
||||||
"github.com/notaryproject/notation-go/registry"
|
"github.com/notaryproject/notation-go/registry"
|
||||||
|
@ -45,7 +43,7 @@ func Example_signWithTimestamp() {
|
||||||
// exampleSigner is a notation.Signer given key and X509 certificate chain.
|
// exampleSigner is a notation.Signer given key and X509 certificate chain.
|
||||||
// Users should replace `exampleCertTuple.PrivateKey` with their own private
|
// Users should replace `exampleCertTuple.PrivateKey` with their own private
|
||||||
// key and replace `exampleCerts` with the corresponding full certificate
|
// key and replace `exampleCerts` with the corresponding full certificate
|
||||||
// chain, following the Notary Project certificate requirements:
|
// chain, following the Notary certificate requirements:
|
||||||
// https://github.com/notaryproject/notaryproject/blob/v1.0.0/specs/signature-specification.md#certificate-requirements
|
// https://github.com/notaryproject/notaryproject/blob/v1.0.0/specs/signature-specification.md#certificate-requirements
|
||||||
exampleSigner, err := signer.NewGenericSigner(exampleCertTuple.PrivateKey, exampleCerts)
|
exampleSigner, err := signer.NewGenericSigner(exampleCertTuple.PrivateKey, exampleCerts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -79,35 +77,23 @@ func Example_signWithTimestamp() {
|
||||||
tsaRootCAs := x509.NewCertPool()
|
tsaRootCAs := x509.NewCertPool()
|
||||||
tsaRootCAs.AddCert(tsaRootCert)
|
tsaRootCAs.AddCert(tsaRootCert)
|
||||||
|
|
||||||
// enable timestamping certificate chain revocation check
|
|
||||||
tsaRevocationValidator, err := revocation.NewWithOptions(revocation.Options{
|
|
||||||
CertChainPurpose: purpose.Timestamping,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
panic(err) // Handle error
|
|
||||||
}
|
|
||||||
|
|
||||||
// exampleSignOptions is an example of notation.SignOptions.
|
// exampleSignOptions is an example of notation.SignOptions.
|
||||||
exampleSignOptions := notation.SignOptions{
|
exampleSignOptions := notation.SignOptions{
|
||||||
SignerSignOptions: notation.SignerSignOptions{
|
SignerSignOptions: notation.SignerSignOptions{
|
||||||
SignatureMediaType: exampleSignatureMediaType,
|
SignatureMediaType: exampleSignatureMediaType,
|
||||||
Timestamper: httpTimestamper,
|
Timestamper: httpTimestamper,
|
||||||
TSARootCAs: tsaRootCAs,
|
TSARootCAs: tsaRootCAs,
|
||||||
TSARevocationValidator: tsaRevocationValidator,
|
|
||||||
},
|
},
|
||||||
ArtifactReference: exampleArtifactReference,
|
ArtifactReference: exampleArtifactReference,
|
||||||
}
|
}
|
||||||
|
|
||||||
targetManifestDesc, sigManifestDesc, err := notation.SignOCI(context.Background(), exampleSigner, exampleRepo, exampleSignOptions)
|
targetDesc, err := notation.Sign(context.Background(), exampleSigner, exampleRepo, exampleSignOptions)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err) // Handle error
|
panic(err) // Handle error
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Println("Successfully signed")
|
fmt.Println("Successfully signed")
|
||||||
fmt.Println("targetManifestDesc.MediaType:", targetManifestDesc.MediaType)
|
fmt.Println("targetDesc MediaType:", targetDesc.MediaType)
|
||||||
fmt.Println("targetManifestDesc.Digest:", targetManifestDesc.Digest)
|
fmt.Println("targetDesc Digest:", targetDesc.Digest)
|
||||||
fmt.Println("targetManifestDesc.Size:", targetManifestDesc.Size)
|
fmt.Println("targetDesc Size:", targetDesc.Size)
|
||||||
fmt.Println("sigManifestDesc.MediaType:", sigManifestDesc.MediaType)
|
|
||||||
fmt.Println("sigManifestDesc.Digest:", sigManifestDesc.Digest)
|
|
||||||
fmt.Println("sigManifestDesc.Size:", sigManifestDesc.Size)
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,154 +0,0 @@
|
||||||
// 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_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/notaryproject/notation-core-go/signature/jws"
|
|
||||||
"github.com/notaryproject/notation-go"
|
|
||||||
"github.com/notaryproject/notation-go/dir"
|
|
||||||
"github.com/notaryproject/notation-go/verifier"
|
|
||||||
"github.com/notaryproject/notation-go/verifier/trustpolicy"
|
|
||||||
"github.com/notaryproject/notation-go/verifier/truststore"
|
|
||||||
)
|
|
||||||
|
|
||||||
// exampleBlobPolicyDocument is an example of a valid blob trust policy document.
|
|
||||||
// blob trust policy document should follow this spec:
|
|
||||||
// https://github.com/notaryproject/specifications/tree/9c81dc773508dedc5a81c02c8d805de04f65050b/specs/trust-store-trust-policy.md#blob-trust-policy
|
|
||||||
var exampleBlobPolicyDocument = trustpolicy.BlobDocument{
|
|
||||||
Version: "1.0",
|
|
||||||
TrustPolicies: []trustpolicy.BlobTrustPolicy{
|
|
||||||
{
|
|
||||||
Name: "test-statement-name",
|
|
||||||
SignatureVerification: trustpolicy.SignatureVerification{VerificationLevel: trustpolicy.LevelStrict.Name, Override: map[trustpolicy.ValidationType]trustpolicy.ValidationAction{trustpolicy.TypeRevocation: trustpolicy.ActionSkip}},
|
|
||||||
TrustStores: []string{"ca:valid-trust-store"},
|
|
||||||
TrustedIdentities: []string{"*"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
// ExampleVerifyBlob demonstrates how to use [notation.VerifyBlob] to verify a
|
|
||||||
// signature of an arbitrary blob.
|
|
||||||
func Example_verifyBlob() {
|
|
||||||
// Both COSE ("application/cose") and JWS ("application/jose+json")
|
|
||||||
// signature mediaTypes are supported.
|
|
||||||
exampleSignatureMediaType := jws.MediaTypeEnvelope
|
|
||||||
|
|
||||||
// exampleSignatureEnvelope is a valid signature envelope.
|
|
||||||
exampleSignatureEnvelope := getSignatureEnvelope()
|
|
||||||
|
|
||||||
// createTrustStoreForBlobVerify creates a trust store directory for demo purpose.
|
|
||||||
// Users could use the default trust store from Notary Project and add trusted
|
|
||||||
// certificates into it following the trust store spec:
|
|
||||||
// https://github.com/notaryproject/specifications/tree/9c81dc773508dedc5a81c02c8d805de04f65050b/specs/trust-store-trust-policy.md#trust-store
|
|
||||||
if err := createTrustStoreForBlobVerify(); err != nil {
|
|
||||||
panic(err) // Handle error
|
|
||||||
}
|
|
||||||
|
|
||||||
// exampleVerifier implements [notation.Verify] and [notation.VerifyBlob].
|
|
||||||
exampleVerifier, err := verifier.NewVerifierWithOptions(truststore.NewX509TrustStore(dir.ConfigFS()), verifier.VerifierOptions{
|
|
||||||
BlobTrustPolicy: &exampleBlobPolicyDocument,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
panic(err) // Handle error
|
|
||||||
}
|
|
||||||
|
|
||||||
// exampleReader reads the data that needs to be verified.
|
|
||||||
// This data can be in a file or in memory.
|
|
||||||
exampleReader := strings.NewReader("example blob")
|
|
||||||
|
|
||||||
// exampleVerifyOptions is an example of [notation.VerifyBlobOptions]
|
|
||||||
exampleVerifyOptions := notation.VerifyBlobOptions{
|
|
||||||
BlobVerifierVerifyOptions: notation.BlobVerifierVerifyOptions{
|
|
||||||
SignatureMediaType: exampleSignatureMediaType,
|
|
||||||
TrustPolicyName: "test-statement-name",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
// upon successful verification, the signature verification outcome is
|
|
||||||
// returned.
|
|
||||||
_, outcome, err := notation.VerifyBlob(context.Background(), exampleVerifier, exampleReader, []byte(exampleSignatureEnvelope), exampleVerifyOptions)
|
|
||||||
if err != nil {
|
|
||||||
panic(err) // Handle error
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Println("Successfully verified")
|
|
||||||
|
|
||||||
// a peek of the payload inside the signature envelope
|
|
||||||
fmt.Println("payload ContentType:", outcome.EnvelopeContent.Payload.ContentType)
|
|
||||||
|
|
||||||
// Note, upon successful verification, payload.TargetArtifact from the
|
|
||||||
// signature envelope matches exactly with our exampleTargetDescriptor.
|
|
||||||
// (This check has been done for the user inside verifier.Verify.)
|
|
||||||
fmt.Println("payload Content:", string(outcome.EnvelopeContent.Payload.Content))
|
|
||||||
|
|
||||||
// Output:
|
|
||||||
// Successfully verified
|
|
||||||
// payload ContentType: application/vnd.cncf.notary.payload.v1+json
|
|
||||||
// payload Content: {"targetArtifact":{"digest":"sha384:b8ab24dafba5cf7e4c89c562f811cf10493d4203da982d3b1345f366ca863d9c2ed323dbd0fb7ff83a80302ceffa5a61","mediaType":"video/mp4","size":12}}
|
|
||||||
}
|
|
||||||
|
|
||||||
func createTrustStoreForBlobVerify() error {
|
|
||||||
// changing the path of the trust store for demo purpose.
|
|
||||||
// Users could keep the default value, i.e. os.UserConfigDir.
|
|
||||||
dir.UserConfigDir = "tmp"
|
|
||||||
|
|
||||||
// an example of a valid X509 self-signed certificate for demo purpose ONLY.
|
|
||||||
// (This self-signed cert is paired with the private key used to
|
|
||||||
// generate the `exampleSignatureEnvelopePem` above.)
|
|
||||||
// Users should replace `exampleX509Certificate` with their own trusted
|
|
||||||
// certificate and add to the trust store, following the
|
|
||||||
// Notary Project certificate requirements:
|
|
||||||
// https://github.com/notaryproject/specifications/tree/9c81dc773508dedc5a81c02c8d805de04f65050b/specs/signature-specification.md#certificate-requirements
|
|
||||||
exampleX509Certificate := `-----BEGIN CERTIFICATE-----
|
|
||||||
MIIEbDCCAtSgAwIBAgIBUzANBgkqhkiG9w0BAQsFADBkMQswCQYDVQQGEwJVUzEL
|
|
||||||
MAkGA1UECBMCV0ExEDAOBgNVBAcTB1NlYXR0bGUxDzANBgNVBAoTBk5vdGFyeTEl
|
|
||||||
MCMGA1UEAxMcTm90YXRpb24gRXhhbXBsZSBzZWxmLXNpZ25lZDAgFw0yNDA0MDQy
|
|
||||||
MTIwMjBaGA8yMTI0MDQwNDIxMjAyMFowZDELMAkGA1UEBhMCVVMxCzAJBgNVBAgT
|
|
||||||
AldBMRAwDgYDVQQHEwdTZWF0dGxlMQ8wDQYDVQQKEwZOb3RhcnkxJTAjBgNVBAMT
|
|
||||||
HE5vdGF0aW9uIEV4YW1wbGUgc2VsZi1zaWduZWQwggGiMA0GCSqGSIb3DQEBAQUA
|
|
||||||
A4IBjwAwggGKAoIBgQDGIiN4yCjSVqFELZwxK/BMb8BokP587L8oPrZ1g8H7LudB
|
|
||||||
moLNDT7vF9xccbCfU3yNuOd0WaOgnENiCs81VHidyJsj1Oz3u+0Zn3ng7V+uZr6m
|
|
||||||
AIO74efA9ClMiY4i4HIt8IAZF57AL2mzDnCITgSWxikf030Il85MI42STvA+qYuz
|
|
||||||
ZEOp3XvKo8bDgQFvbtgK0HYYMfrka7VDmIWVo0rBMGm5btI8HOYQ0r9aqsrCxLAv
|
|
||||||
1AQeOQm+wbRcp4R5PIUJr+REGn7JCbOyXg/7qqHXKKmvV5yrGaraw8gZ5pqP/RHK
|
|
||||||
XUJIfvD0Vf2epJmsvC+6vXkSWtz+cA8J4GQx4J4SXL57hoYkC5qv39SOLzlWls3I
|
|
||||||
6fgeO+SZ0sceMd8NKlom/L5eOJBfB3bTQB83hq/3bRtjT7/qCMsL3VcndKkS+vGF
|
|
||||||
JPw5uTH+pmBgHrLr6tRoRRjwRFuZ0dO05AbdjCaxgVDtFI3wNbaXn/1VlRGySQIS
|
|
||||||
UNWxCrUsSzndeqwmjqsCAwEAAaMnMCUwDgYDVR0PAQH/BAQDAgeAMBMGA1UdJQQM
|
|
||||||
MAoGCCsGAQUFBwMDMA0GCSqGSIb3DQEBCwUAA4IBgQBdi0SaJAaeKBB0I+Fjcbmc
|
|
||||||
4zRvHE4GDSMSDnAK97nrZCZ9iwKuY4x6mv9lwQe2P3VXROoL9JmONNf0yaObOwQj
|
|
||||||
ILGnbe2rzYtUardz2gzh+6KNzJHspRvk1f06mp4496XQ3STMRSr8kno1svKQMy0Y
|
|
||||||
FRsGMKs4fWHavIAqNXg9ymrZvvXiatN2UiVtAA/jBFScZAWskeb2WHNzORi7H5Z1
|
|
||||||
mp5+IlNYQpzdIu/dvLVxzhh2UvkRdsQqsMgt/MOU84RncwUNZM4yI5EGPoaSJdsj
|
|
||||||
AGNd+UV6ur7QmVI2Q9EZNRlaDJtaoZmKns5j1SlmDXWKbdRmw42ORDudODj/pHA9
|
|
||||||
+u+ca9t3uLsbqO9yPm8m+6fyxffWS11QAH6O7EjydJWcEe5tYkPpL6kcaEyQKESm
|
|
||||||
5CDlsk+W3ElpaUu6tsnGKODvgdAN3m0noC+qxzCMqoCM4+M5V6OptR98MDl2FK0B
|
|
||||||
5+WF6YHBxf/uqDvFktUczjrIWuyfECywp05bpGAErGE=
|
|
||||||
-----END CERTIFICATE-----`
|
|
||||||
|
|
||||||
// Adding the certificate into the trust store.
|
|
||||||
if err := os.MkdirAll("tmp/truststore/x509/ca/valid-trust-store", 0700); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return os.WriteFile("tmp/truststore/x509/ca/valid-trust-store/NotationBlobExample.pem", []byte(exampleX509Certificate), 0600)
|
|
||||||
}
|
|
||||||
|
|
||||||
func getSignatureEnvelope() string {
|
|
||||||
return `{"payload":"eyJ0YXJnZXRBcnRpZmFjdCI6eyJkaWdlc3QiOiJzaGEzODQ6YjhhYjI0ZGFmYmE1Y2Y3ZTRjODljNTYyZjgxMWNmMTA0OTNkNDIwM2RhOTgyZDNiMTM0NWYzNjZjYTg2M2Q5YzJlZDMyM2RiZDBmYjdmZjgzYTgwMzAyY2VmZmE1YTYxIiwibWVkaWFUeXBlIjoidmlkZW8vbXA0Iiwic2l6ZSI6MTJ9fQ","protected":"eyJhbGciOiJQUzM4NCIsImNyaXQiOlsiaW8uY25jZi5ub3Rhcnkuc2lnbmluZ1NjaGVtZSJdLCJjdHkiOiJhcHBsaWNhdGlvbi92bmQuY25jZi5ub3RhcnkucGF5bG9hZC52MStqc29uIiwiaW8uY25jZi5ub3Rhcnkuc2lnbmluZ1NjaGVtZSI6Im5vdGFyeS54NTA5IiwiaW8uY25jZi5ub3Rhcnkuc2lnbmluZ1RpbWUiOiIyMDI0LTA0LTA0VDE0OjIwOjIxLTA3OjAwIn0","header":{"x5c":["MIIEbDCCAtSgAwIBAgIBUzANBgkqhkiG9w0BAQsFADBkMQswCQYDVQQGEwJVUzELMAkGA1UECBMCV0ExEDAOBgNVBAcTB1NlYXR0bGUxDzANBgNVBAoTBk5vdGFyeTElMCMGA1UEAxMcTm90YXRpb24gRXhhbXBsZSBzZWxmLXNpZ25lZDAgFw0yNDA0MDQyMTIwMjBaGA8yMTI0MDQwNDIxMjAyMFowZDELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAldBMRAwDgYDVQQHEwdTZWF0dGxlMQ8wDQYDVQQKEwZOb3RhcnkxJTAjBgNVBAMTHE5vdGF0aW9uIEV4YW1wbGUgc2VsZi1zaWduZWQwggGiMA0GCSqGSIb3DQEBAQUAA4IBjwAwggGKAoIBgQDGIiN4yCjSVqFELZwxK/BMb8BokP587L8oPrZ1g8H7LudBmoLNDT7vF9xccbCfU3yNuOd0WaOgnENiCs81VHidyJsj1Oz3u+0Zn3ng7V+uZr6mAIO74efA9ClMiY4i4HIt8IAZF57AL2mzDnCITgSWxikf030Il85MI42STvA+qYuzZEOp3XvKo8bDgQFvbtgK0HYYMfrka7VDmIWVo0rBMGm5btI8HOYQ0r9aqsrCxLAv1AQeOQm+wbRcp4R5PIUJr+REGn7JCbOyXg/7qqHXKKmvV5yrGaraw8gZ5pqP/RHKXUJIfvD0Vf2epJmsvC+6vXkSWtz+cA8J4GQx4J4SXL57hoYkC5qv39SOLzlWls3I6fgeO+SZ0sceMd8NKlom/L5eOJBfB3bTQB83hq/3bRtjT7/qCMsL3VcndKkS+vGFJPw5uTH+pmBgHrLr6tRoRRjwRFuZ0dO05AbdjCaxgVDtFI3wNbaXn/1VlRGySQISUNWxCrUsSzndeqwmjqsCAwEAAaMnMCUwDgYDVR0PAQH/BAQDAgeAMBMGA1UdJQQMMAoGCCsGAQUFBwMDMA0GCSqGSIb3DQEBCwUAA4IBgQBdi0SaJAaeKBB0I+Fjcbmc4zRvHE4GDSMSDnAK97nrZCZ9iwKuY4x6mv9lwQe2P3VXROoL9JmONNf0yaObOwQjILGnbe2rzYtUardz2gzh+6KNzJHspRvk1f06mp4496XQ3STMRSr8kno1svKQMy0YFRsGMKs4fWHavIAqNXg9ymrZvvXiatN2UiVtAA/jBFScZAWskeb2WHNzORi7H5Z1mp5+IlNYQpzdIu/dvLVxzhh2UvkRdsQqsMgt/MOU84RncwUNZM4yI5EGPoaSJdsjAGNd+UV6ur7QmVI2Q9EZNRlaDJtaoZmKns5j1SlmDXWKbdRmw42ORDudODj/pHA9+u+ca9t3uLsbqO9yPm8m+6fyxffWS11QAH6O7EjydJWcEe5tYkPpL6kcaEyQKESm5CDlsk+W3ElpaUu6tsnGKODvgdAN3m0noC+qxzCMqoCM4+M5V6OptR98MDl2FK0B5+WF6YHBxf/uqDvFktUczjrIWuyfECywp05bpGAErGE="],"io.cncf.notary.signingAgent":"example signing agent"},"signature":"liOjdgQ9BKuQTZGXRh3o6P8AMUIq_MKQReEcqA5h8M4RYs3DV_wXfaLCr2x_NRcwjTZsoO1_J77hmzkkk4L0IuFP8Qw0KKtmc83G0yFi4yYV5fwzrIbnhC2GRLuqLPnK-C4qYmv52ld3ebvo7XWwRHu30-VXePmTRFp6iG-eSAgkNgwhxSZ0ZmTFLG3ceNiX2bxpLHlXdPwA3aFKbd6nKrzo4CZ1ZyLNmAIaoA5-kmc0Hyt45trpxaaiWusI_pcTLw71YCqEAs32tEq3q6hRAgAZZN-Qvm9GyNp9EuaPiKjMbJFqtjome5ITxyNd-5t09dDCUgSe3t-iqv2Blm4E080AP1TYwUKLYklGniUP1dAtOau5G2juZLpl7tr4LQ99mycflnAmV7e79eEWXffvy5EAl77dW4_vM7lEemm08m2wddGuDOWXYb1j1r2_a5Xb92umHq6ZMhAp200A0pUkm9640x8z5jdudi_7KeezdqUK7ZMmSxHohiylyKD_20Cy"}`
|
|
||||||
}
|
|
|
@ -39,9 +39,9 @@ func Example_verifyWithTimestamp() {
|
||||||
// timestamping configurations.
|
// timestamping configurations.
|
||||||
// trust policy document should follow this spec:
|
// trust policy document should follow this spec:
|
||||||
// https://github.com/notaryproject/notaryproject/blob/v1.0.0/specs/trust-store-trust-policy.md#trust-policy
|
// https://github.com/notaryproject/notaryproject/blob/v1.0.0/specs/trust-store-trust-policy.md#trust-policy
|
||||||
examplePolicyDocument := trustpolicy.OCIDocument{
|
examplePolicyDocument := trustpolicy.Document{
|
||||||
Version: "1.0",
|
Version: "1.0",
|
||||||
TrustPolicies: []trustpolicy.OCITrustPolicy{
|
TrustPolicies: []trustpolicy.TrustPolicy{
|
||||||
{
|
{
|
||||||
Name: "test-statement-name",
|
Name: "test-statement-name",
|
||||||
RegistryScopes: []string{"*"},
|
RegistryScopes: []string{"*"},
|
||||||
|
@ -115,7 +115,7 @@ func generateTrustStoreWithTimestamp() error {
|
||||||
// an example of a valid X509 self-signed certificate for demo purpose ONLY.
|
// an example of a valid X509 self-signed certificate for demo purpose ONLY.
|
||||||
// Users should replace `exampleX509Certificate` with their own trusted
|
// Users should replace `exampleX509Certificate` with their own trusted
|
||||||
// certificate and add to the trust store, following the
|
// certificate and add to the trust store, following the
|
||||||
// Notary Project certificate requirements:
|
// Notary certificate requirements:
|
||||||
// https://github.com/notaryproject/notaryproject/blob/v1.0.0/specs/signature-specification.md#certificate-requirements
|
// https://github.com/notaryproject/notaryproject/blob/v1.0.0/specs/signature-specification.md#certificate-requirements
|
||||||
exampleX509Certificate := `-----BEGIN CERTIFICATE-----
|
exampleX509Certificate := `-----BEGIN CERTIFICATE-----
|
||||||
MIIDQDCCAiigAwIBAgIBUTANBgkqhkiG9w0BAQsFADBOMQswCQYDVQQGEwJVUzEL
|
MIIDQDCCAiigAwIBAgIBUTANBgkqhkiG9w0BAQsFADBOMQswCQYDVQQGEwJVUzEL
|
||||||
|
@ -149,7 +149,7 @@ GLAfj/jSf9OH9VLTPHOS8/N0Ka4=
|
||||||
// an example of a valid TSA root certificate for demo purpose ONLY.
|
// an example of a valid TSA root certificate for demo purpose ONLY.
|
||||||
// Users should replace `exampleTSARootCertificate` with their own trusted
|
// Users should replace `exampleTSARootCertificate` with their own trusted
|
||||||
// TSA root certificate and add to the trust store, following the
|
// TSA root certificate and add to the trust store, following the
|
||||||
// Notary Project certificate requirements:
|
// Notary certificate requirements:
|
||||||
// https://github.com/notaryproject/notaryproject/blob/v1.0.0/specs/signature-specification.md#certificate-requirements
|
// https://github.com/notaryproject/notaryproject/blob/v1.0.0/specs/signature-specification.md#certificate-requirements
|
||||||
exampleTSARootCertificate := `-----BEGIN CERTIFICATE-----
|
exampleTSARootCertificate := `-----BEGIN CERTIFICATE-----
|
||||||
MIIFkDCCA3igAwIBAgIQBZsbV56OITLiOQe9p3d1XDANBgkqhkiG9w0BAQwFADBi
|
MIIFkDCCA3igAwIBAgIQBZsbV56OITLiOQe9p3d1XDANBgkqhkiG9w0BAQwFADBi
|
||||||
|
|
26
go.mod
26
go.mod
|
@ -1,26 +1,26 @@
|
||||||
module github.com/notaryproject/notation-go
|
module github.com/notaryproject/notation-go
|
||||||
|
|
||||||
go 1.23.0
|
go 1.22
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/go-ldap/ldap/v3 v3.4.11
|
github.com/go-ldap/ldap/v3 v3.4.8
|
||||||
github.com/notaryproject/notation-core-go v1.3.0
|
github.com/notaryproject/notation-core-go v1.1.0
|
||||||
github.com/notaryproject/notation-plugin-framework-go v1.0.0
|
github.com/notaryproject/notation-plugin-framework-go v1.0.0
|
||||||
github.com/notaryproject/tspclient-go v1.0.0
|
github.com/notaryproject/tspclient-go v0.2.0
|
||||||
github.com/opencontainers/go-digest v1.0.0
|
github.com/opencontainers/go-digest v1.0.0
|
||||||
github.com/opencontainers/image-spec v1.1.1
|
github.com/opencontainers/image-spec v1.1.0
|
||||||
github.com/veraison/go-cose v1.3.0
|
github.com/veraison/go-cose v1.1.0
|
||||||
golang.org/x/crypto v0.39.0
|
golang.org/x/crypto v0.26.0
|
||||||
golang.org/x/mod v0.25.0
|
golang.org/x/mod v0.20.0
|
||||||
oras.land/oras-go/v2 v2.6.0
|
oras.land/oras-go/v2 v2.5.0
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 // indirect
|
github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 // indirect
|
||||||
github.com/fxamacker/cbor/v2 v2.8.0 // indirect
|
github.com/fxamacker/cbor/v2 v2.7.0 // indirect
|
||||||
github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667 // indirect
|
github.com/go-asn1-ber/asn1-ber v1.5.5 // indirect
|
||||||
github.com/golang-jwt/jwt/v4 v4.5.2 // indirect
|
github.com/golang-jwt/jwt/v4 v4.5.0 // indirect
|
||||||
github.com/google/uuid v1.6.0 // indirect
|
github.com/google/uuid v1.6.0 // indirect
|
||||||
github.com/x448/float16 v0.8.4 // indirect
|
github.com/x448/float16 v0.8.4 // indirect
|
||||||
golang.org/x/sync v0.14.0 // indirect
|
golang.org/x/sync v0.6.0 // indirect
|
||||||
)
|
)
|
||||||
|
|
110
go.sum
110
go.sum
|
@ -2,18 +2,22 @@ github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 h1:mFRzDkZVAjdal+
|
||||||
github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU=
|
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 h1:LHTHcTQiSGT7VVbI0o4wBRNQIgn917usHWOd6VAffYI=
|
||||||
github.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa/go.mod h1:cEWa1LVoE5KvSD9ONXsZrj0z6KqySlCCNKHlLzbqAt4=
|
github.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa/go.mod h1:cEWa1LVoE5KvSD9ONXsZrj0z6KqySlCCNKHlLzbqAt4=
|
||||||
|
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 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/fxamacker/cbor/v2 v2.8.0 h1:fFtUGXUzXPHTIUdne5+zzMPTfffl3RD5qYnkY40vtxU=
|
github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E=
|
||||||
github.com/fxamacker/cbor/v2 v2.8.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ=
|
github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ=
|
||||||
github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667 h1:BP4M0CvQ4S3TGls2FvczZtj5Re/2ZzkV9VwqPHH/3Bo=
|
github.com/go-asn1-ber/asn1-ber v1.5.5 h1:MNHlNMBDgEKD4TcKr36vQN68BA00aDfjIt3/bD50WnA=
|
||||||
github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0=
|
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.11 h1:4k0Yxweg+a3OyBLjdYn5OKglv18JNvfDykSoI8bW0gU=
|
github.com/go-ldap/ldap/v3 v3.4.8 h1:loKJyspcRezt2Q3ZRMq2p/0v8iOurlmeXDPw6fikSvQ=
|
||||||
github.com/go-ldap/ldap/v3 v3.4.11/go.mod h1:bY7t0FLK8OAVpp/vV6sSlpz3EQDGcQwc8pF0ujLgKvM=
|
github.com/go-ldap/ldap/v3 v3.4.8/go.mod h1:qS3Sjlu76eHfHGpUdWkAXQTw4beih+cHsco2jXlIXrk=
|
||||||
github.com/golang-jwt/jwt/v4 v4.5.2 h1:YtQM7lnr8iZ+j5q71MGKkNw9Mn7AjHM68uc9g5fXeUI=
|
github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg=
|
||||||
github.com/golang-jwt/jwt/v4 v4.5.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
|
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 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
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 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8=
|
||||||
github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
||||||
github.com/jcmturner/aescts/v2 v2.0.0 h1:9YKLH6ey7H4eDBXW8khjYslgyqG2xZikXP0EQFKrle8=
|
github.com/jcmturner/aescts/v2 v2.0.0 h1:9YKLH6ey7H4eDBXW8khjYslgyqG2xZikXP0EQFKrle8=
|
||||||
|
@ -28,33 +32,87 @@ github.com/jcmturner/gokrb5/v8 v8.4.4 h1:x1Sv4HaTpepFkXbt2IkL29DXRf8sOfZXo8eRKh6
|
||||||
github.com/jcmturner/gokrb5/v8 v8.4.4/go.mod h1:1btQEpgT6k+unzCwX1KdWMEwPPkkgBtP+F6aCACiMrs=
|
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 h1:7FXXj8Ti1IaVFpSAziCZWNzbNuZmnvw/i6CqLNdWfZY=
|
||||||
github.com/jcmturner/rpc/v2 v2.0.3/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJki3ELg/Hc=
|
github.com/jcmturner/rpc/v2 v2.0.3/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJki3ELg/Hc=
|
||||||
github.com/notaryproject/notation-core-go v1.3.0 h1:mWJaw1QBpBxpjLSiKOjzbZvB+xh2Abzk14FHWQ+9Kfs=
|
github.com/notaryproject/notation-core-go v1.1.0 h1:xCybcONOKcCyPNihJUSa+jRNsyQFNkrk0eJVVs1kWeg=
|
||||||
github.com/notaryproject/notation-core-go v1.3.0/go.mod h1:hzvEOit5lXfNATGNBT8UQRx2J6Fiw/dq/78TQL8aE64=
|
github.com/notaryproject/notation-core-go v1.1.0/go.mod h1:+6AOh41JPrnVLbW/19SJqdhVHwKgIINBO/np0e7nXJA=
|
||||||
github.com/notaryproject/notation-plugin-framework-go v1.0.0 h1:6Qzr7DGXoCgXEQN+1gTZWuJAZvxh3p8Lryjn5FaLzi4=
|
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/notation-plugin-framework-go v1.0.0/go.mod h1:RqWSrTOtEASCrGOEffq0n8pSg2KOgKYiWqFWczRSics=
|
||||||
github.com/notaryproject/tspclient-go v1.0.0 h1:AwQ4x0gX8IHnyiZB1tggpn5NFqHpTEm1SDX8YNv4Dg4=
|
github.com/notaryproject/tspclient-go v0.2.0 h1:g/KpQGmyk/h7j60irIRG1mfWnibNOzJ8WhLqAzuiQAQ=
|
||||||
github.com/notaryproject/tspclient-go v1.0.0/go.mod h1:LGyA/6Kwd2FlM0uk8Vc5il3j0CddbWSHBj/4kxQDbjs=
|
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 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
|
||||||
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
|
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
|
||||||
github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040=
|
github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug=
|
||||||
github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M=
|
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 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
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/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 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
|
||||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||||
github.com/veraison/go-cose v1.3.0 h1:2/H5w8kdSpQJyVtIhx8gmwPJ2uSz1PkyWFx0idbd7rk=
|
github.com/veraison/go-cose v1.1.0 h1:AalPS4VGiKavpAzIlBjrn7bhqXiXi4jbMYY/2+UC+4o=
|
||||||
github.com/veraison/go-cose v1.3.0/go.mod h1:df09OV91aHoQWLmy1KsDdYiagtXgyAwAl8vFeFn1gMc=
|
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 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
|
||||||
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
|
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
|
||||||
golang.org/x/crypto v0.39.0 h1:SHs+kF4LP+f+p14esP5jAoDpHU8Gu/v9lFRK6IT5imM=
|
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||||
golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U=
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
golang.org/x/mod v0.25.0 h1:n7a+ZbQKQA/Ysbyb0/6IbB1H/X41mKgbhfv7AfG/44w=
|
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
golang.org/x/mod v0.25.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=
|
golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=
|
||||||
golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8=
|
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
|
||||||
golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
|
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
|
||||||
golang.org/x/sync v0.14.0 h1:woo0S4Yywslg6hp4eUFjTVOyKt0RookbpAHG4c1HmhQ=
|
golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw=
|
||||||
golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
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 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
oras.land/oras-go/v2 v2.6.0 h1:X4ELRsiGkrbeox69+9tzTu492FMUu7zJQW6eJU+I2oc=
|
oras.land/oras-go/v2 v2.5.0 h1:o8Me9kLY74Vp5uw07QXPiitjsw7qNXi8Twd+19Zf02c=
|
||||||
oras.land/oras-go/v2 v2.6.0/go.mod h1:magiQDfG6H1O9APp+rOsvCPcW1GD2MM7vgnKY0Y+u1o=
|
oras.land/oras-go/v2 v2.5.0/go.mod h1:z4eisnLP530vwIOUOJeBIj0aGI0L1C3d53atvCBqZHg=
|
||||||
|
|
|
@ -15,7 +15,6 @@ package file
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
|
||||||
"io"
|
"io"
|
||||||
"io/fs"
|
"io/fs"
|
||||||
"os"
|
"os"
|
||||||
|
@ -24,11 +23,6 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
|
||||||
// tempFileNamePrefix is the prefix of the temporary file
|
|
||||||
tempFileNamePrefix = "notation-*"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ErrNotRegularFile is returned when the file is not an regular file.
|
// ErrNotRegularFile is returned when the file is not an regular file.
|
||||||
var ErrNotRegularFile = errors.New("not regular file")
|
var ErrNotRegularFile = errors.New("not regular file")
|
||||||
|
|
||||||
|
@ -116,38 +110,3 @@ func CopyDirToDir(src, dst string) error {
|
||||||
func TrimFileExtension(fileName string) string {
|
func TrimFileExtension(fileName string) string {
|
||||||
return strings.TrimSuffix(fileName, filepath.Ext(fileName))
|
return strings.TrimSuffix(fileName, filepath.Ext(fileName))
|
||||||
}
|
}
|
||||||
|
|
||||||
// WriteFile writes content to a temporary file and moves it to path.
|
|
||||||
// If path already exists and is a file, WriteFile overwrites it.
|
|
||||||
//
|
|
||||||
// Parameters:
|
|
||||||
// - tempDir is the directory to create the temporary file. It should be
|
|
||||||
// in the same mount point as path. If tempDir is empty, the default
|
|
||||||
// directory for temporary files is used.
|
|
||||||
// - path is the destination file path.
|
|
||||||
// - content is the content to write.
|
|
||||||
func WriteFile(tempDir, path string, content []byte) (writeErr error) {
|
|
||||||
tempFile, err := os.CreateTemp(tempDir, tempFileNamePrefix)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to create temp file: %w", err)
|
|
||||||
}
|
|
||||||
defer func() {
|
|
||||||
// remove the temp file in case of error
|
|
||||||
if writeErr != nil {
|
|
||||||
tempFile.Close()
|
|
||||||
os.Remove(tempFile.Name())
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
if _, err := tempFile.Write(content); err != nil {
|
|
||||||
return fmt.Errorf("failed to write content to temp file: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// close before moving
|
|
||||||
if err := tempFile.Close(); err != nil {
|
|
||||||
return fmt.Errorf("failed to close temp file: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// rename is atomic on UNIX-like platforms
|
|
||||||
return os.Rename(tempFile.Name(), path)
|
|
||||||
}
|
|
||||||
|
|
|
@ -18,7 +18,6 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"runtime"
|
"runtime"
|
||||||
"strings"
|
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -27,10 +26,7 @@ func TestCopyToDir(t *testing.T) {
|
||||||
tempDir := t.TempDir()
|
tempDir := t.TempDir()
|
||||||
data := []byte("data")
|
data := []byte("data")
|
||||||
filename := filepath.Join(tempDir, "a", "file.txt")
|
filename := filepath.Join(tempDir, "a", "file.txt")
|
||||||
if err := os.MkdirAll(filepath.Dir(filename), 0700); err != nil {
|
if err := writeFile(filename, data); err != nil {
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
if err := WriteFile(tempDir, filename, data); err != nil {
|
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -49,10 +45,7 @@ func TestCopyToDir(t *testing.T) {
|
||||||
destDir := t.TempDir()
|
destDir := t.TempDir()
|
||||||
data := []byte("data")
|
data := []byte("data")
|
||||||
filename := filepath.Join(tempDir, "a", "file.txt")
|
filename := filepath.Join(tempDir, "a", "file.txt")
|
||||||
if err := os.MkdirAll(filepath.Dir(filename), 0700); err != nil {
|
if err := writeFile(filename, data); err != nil {
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
if err := WriteFile(tempDir, filename, data); err != nil {
|
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -84,10 +77,7 @@ func TestCopyToDir(t *testing.T) {
|
||||||
data := []byte("data")
|
data := []byte("data")
|
||||||
// prepare file
|
// prepare file
|
||||||
filename := filepath.Join(tempDir, "a", "file.txt")
|
filename := filepath.Join(tempDir, "a", "file.txt")
|
||||||
if err := os.MkdirAll(filepath.Dir(filename), 0700); err != nil {
|
if err := writeFile(filename, data); err != nil {
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
if err := WriteFile(tempDir, filename, data); err != nil {
|
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
// forbid reading
|
// forbid reading
|
||||||
|
@ -110,10 +100,7 @@ func TestCopyToDir(t *testing.T) {
|
||||||
data := []byte("data")
|
data := []byte("data")
|
||||||
// prepare file
|
// prepare file
|
||||||
filename := filepath.Join(tempDir, "a", "file.txt")
|
filename := filepath.Join(tempDir, "a", "file.txt")
|
||||||
if err := os.MkdirAll(filepath.Dir(filename), 0700); err != nil {
|
if err := writeFile(filename, data); err != nil {
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
if err := WriteFile(tempDir, filename, data); err != nil {
|
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
// forbid dest directory operation
|
// forbid dest directory operation
|
||||||
|
@ -136,10 +123,7 @@ func TestCopyToDir(t *testing.T) {
|
||||||
data := []byte("data")
|
data := []byte("data")
|
||||||
// prepare file
|
// prepare file
|
||||||
filename := filepath.Join(tempDir, "a", "file.txt")
|
filename := filepath.Join(tempDir, "a", "file.txt")
|
||||||
if err := os.MkdirAll(filepath.Dir(filename), 0700); err != nil {
|
if err := writeFile(filename, data); err != nil {
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
if err := WriteFile(tempDir, filename, data); err != nil {
|
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
// forbid writing to destTempDir
|
// forbid writing to destTempDir
|
||||||
|
@ -156,10 +140,7 @@ func TestCopyToDir(t *testing.T) {
|
||||||
tempDir := t.TempDir()
|
tempDir := t.TempDir()
|
||||||
data := []byte("data")
|
data := []byte("data")
|
||||||
filename := filepath.Join(tempDir, "a", "file.txt")
|
filename := filepath.Join(tempDir, "a", "file.txt")
|
||||||
if err := os.MkdirAll(filepath.Dir(filename), 0700); err != nil {
|
if err := writeFile(filename, data); err != nil {
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
if err := WriteFile(tempDir, filename, data); err != nil {
|
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -180,29 +161,6 @@ func TestFileNameWithoutExtension(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestWriteFile(t *testing.T) {
|
|
||||||
tempDir := t.TempDir()
|
|
||||||
content := []byte("test WriteFile")
|
|
||||||
|
|
||||||
t.Run("permission denied", func(t *testing.T) {
|
|
||||||
if runtime.GOOS == "windows" {
|
|
||||||
t.Skip("skipping test on Windows")
|
|
||||||
}
|
|
||||||
err := os.Chmod(tempDir, 0)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
err = WriteFile(tempDir, filepath.Join(tempDir, "testFile"), content)
|
|
||||||
if err == nil || !strings.Contains(err.Error(), "permission denied") {
|
|
||||||
t.Fatalf("expected permission denied error, but got %s", err)
|
|
||||||
}
|
|
||||||
err = os.Chmod(tempDir, 0700)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func validFileContent(t *testing.T, filename string, content []byte) {
|
func validFileContent(t *testing.T, filename string, content []byte) {
|
||||||
b, err := os.ReadFile(filename)
|
b, err := os.ReadFile(filename)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -212,3 +170,10 @@ func validFileContent(t *testing.T, filename string, content []byte) {
|
||||||
t.Fatal("file content is not correct")
|
t.Fatal("file content is not correct")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
|
@ -1,53 +0,0 @@
|
||||||
// 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 provides a LimitWriter that writes to an underlying writer up to
|
|
||||||
// a limit.
|
|
||||||
|
|
||||||
package io
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"io"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ErrLimitExceeded is returned when the write limit is exceeded.
|
|
||||||
var ErrLimitExceeded = errors.New("write limit exceeded")
|
|
||||||
|
|
||||||
// LimitedWriter is a writer that writes to an underlying writer up to a limit.
|
|
||||||
type LimitedWriter struct {
|
|
||||||
W io.Writer // underlying writer
|
|
||||||
N int64 // remaining bytes
|
|
||||||
}
|
|
||||||
|
|
||||||
// LimitWriter returns a new LimitWriter that writes to w.
|
|
||||||
//
|
|
||||||
// parameters:
|
|
||||||
// w: the writer to write to
|
|
||||||
// limit: the maximum number of bytes to write
|
|
||||||
func LimitWriter(w io.Writer, limit int64) *LimitedWriter {
|
|
||||||
return &LimitedWriter{W: w, N: limit}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Write writes p to the underlying writer up to the limit.
|
|
||||||
func (l *LimitedWriter) Write(p []byte) (int, error) {
|
|
||||||
if l.N <= 0 {
|
|
||||||
return 0, ErrLimitExceeded
|
|
||||||
}
|
|
||||||
if int64(len(p)) > l.N {
|
|
||||||
p = p[:l.N]
|
|
||||||
}
|
|
||||||
n, err := l.W.Write(p)
|
|
||||||
l.N -= int64(n)
|
|
||||||
return n, err
|
|
||||||
}
|
|
|
@ -1,67 +0,0 @@
|
||||||
// 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 (
|
|
||||||
"bytes"
|
|
||||||
"errors"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestLimitWriter(t *testing.T) {
|
|
||||||
limit := int64(10)
|
|
||||||
|
|
||||||
tests := []struct {
|
|
||||||
input string
|
|
||||||
expected string
|
|
||||||
written int
|
|
||||||
}{
|
|
||||||
{"hello", "hello", 5},
|
|
||||||
{" world", " world", 6},
|
|
||||||
{"!", "!", 1},
|
|
||||||
{"1234567891011", "1234567891", 10},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tt := range tests {
|
|
||||||
var buf bytes.Buffer
|
|
||||||
lw := LimitWriter(&buf, limit)
|
|
||||||
n, err := lw.Write([]byte(tt.input))
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unexpected error: %v", err)
|
|
||||||
}
|
|
||||||
if n != tt.written {
|
|
||||||
t.Errorf("expected %d bytes written, got %d", tt.written, n)
|
|
||||||
}
|
|
||||||
if buf.String() != tt.expected {
|
|
||||||
t.Errorf("expected buffer %q, got %q", tt.expected, buf.String())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestLimitWriterFailed(t *testing.T) {
|
|
||||||
limit := int64(10)
|
|
||||||
longString := "1234567891011"
|
|
||||||
|
|
||||||
var buf bytes.Buffer
|
|
||||||
lw := LimitWriter(&buf, limit)
|
|
||||||
_, err := lw.Write([]byte(longString))
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unexpected error: %v", err)
|
|
||||||
}
|
|
||||||
_, err = lw.Write([]byte(longString))
|
|
||||||
expectedErr := errors.New("write limit exceeded")
|
|
||||||
if err.Error() != expectedErr.Error() {
|
|
||||||
t.Errorf("expected error %v, got %v", expectedErr, err)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,24 +0,0 @@
|
||||||
// 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 pkix
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func FuzzParseDistinguishedName(f *testing.F) {
|
|
||||||
f.Fuzz(func(t *testing.T, name string) {
|
|
||||||
_, _ = ParseDistinguishedName(name)
|
|
||||||
})
|
|
||||||
}
|
|
252
notation.go
252
notation.go
|
@ -12,7 +12,7 @@
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
// Package notation provides signer and verifier for notation Sign
|
// Package notation provides signer and verifier for notation Sign
|
||||||
// and Verification. It supports both OCI artifact and arbitrary blob.
|
// and Verification.
|
||||||
package notation
|
package notation
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
@ -23,15 +23,12 @@ import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
|
||||||
"mime"
|
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
orasRegistry "oras.land/oras-go/v2/registry"
|
orasRegistry "oras.land/oras-go/v2/registry"
|
||||||
"oras.land/oras-go/v2/registry/remote"
|
"oras.land/oras-go/v2/registry/remote"
|
||||||
|
|
||||||
"github.com/notaryproject/notation-core-go/revocation"
|
|
||||||
"github.com/notaryproject/notation-core-go/signature"
|
"github.com/notaryproject/notation-core-go/signature"
|
||||||
"github.com/notaryproject/notation-core-go/signature/cose"
|
"github.com/notaryproject/notation-core-go/signature/cose"
|
||||||
"github.com/notaryproject/notation-core-go/signature/jws"
|
"github.com/notaryproject/notation-core-go/signature/jws"
|
||||||
|
@ -48,7 +45,7 @@ var errDoneVerification = errors.New("done verification")
|
||||||
|
|
||||||
var reservedAnnotationPrefixes = [...]string{"io.cncf.notary"}
|
var reservedAnnotationPrefixes = [...]string{"io.cncf.notary"}
|
||||||
|
|
||||||
// SignerSignOptions contains parameters for [Signer] and [BlobSigner].
|
// SignerSignOptions contains parameters for Signer.Sign.
|
||||||
type SignerSignOptions struct {
|
type SignerSignOptions struct {
|
||||||
// SignatureMediaType is the envelope type of the signature.
|
// SignatureMediaType is the envelope type of the signature.
|
||||||
// Currently, both `application/jose+json` and `application/cose` are
|
// Currently, both `application/jose+json` and `application/cose` are
|
||||||
|
@ -70,11 +67,6 @@ type SignerSignOptions struct {
|
||||||
|
|
||||||
// TSARootCAs is the cert pool holding caller's TSA trust anchor
|
// TSARootCAs is the cert pool holding caller's TSA trust anchor
|
||||||
TSARootCAs *x509.CertPool
|
TSARootCAs *x509.CertPool
|
||||||
|
|
||||||
// TSARevocationValidator is used for validating revocation status of
|
|
||||||
// timestamping certificate chain with context during signing.
|
|
||||||
// When present, only used when timestamping is performed.
|
|
||||||
TSARevocationValidator revocation.Validator
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Signer is a generic interface for signing an OCI artifact.
|
// Signer is a generic interface for signing an OCI artifact.
|
||||||
|
@ -86,38 +78,6 @@ type Signer interface {
|
||||||
Sign(ctx context.Context, desc ocispec.Descriptor, opts SignerSignOptions) ([]byte, *signature.SignerInfo, error)
|
Sign(ctx context.Context, desc ocispec.Descriptor, opts SignerSignOptions) ([]byte, *signature.SignerInfo, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SignBlobOptions contains parameters for [notation.SignBlob].
|
|
||||||
type SignBlobOptions struct {
|
|
||||||
SignerSignOptions
|
|
||||||
|
|
||||||
// ContentMediaType is the media-type of the blob being signed.
|
|
||||||
ContentMediaType string
|
|
||||||
|
|
||||||
// UserMetadata contains key-value pairs that are added to the signature
|
|
||||||
// payload
|
|
||||||
UserMetadata map[string]string
|
|
||||||
}
|
|
||||||
|
|
||||||
// BlobDescriptorGenerator creates descriptor using the digest Algorithm.
|
|
||||||
// Below is the example of minimal descriptor, it must contain mediatype,
|
|
||||||
// digest and size of the artifact.
|
|
||||||
//
|
|
||||||
// {
|
|
||||||
// "mediaType": "application/octet-stream",
|
|
||||||
// "digest": "sha256:2f3a23b6373afb134ddcd864be8e037e34a662d090d33ee849471ff73c873345",
|
|
||||||
// "size": 1024
|
|
||||||
// }
|
|
||||||
type BlobDescriptorGenerator func(digest.Algorithm) (ocispec.Descriptor, error)
|
|
||||||
|
|
||||||
// BlobSigner is a generic interface for signing arbitrary data.
|
|
||||||
// The interface allows signing with local or remote keys,
|
|
||||||
// and packing in various signature formats.
|
|
||||||
type BlobSigner interface {
|
|
||||||
// SignBlob signs the descriptor returned by genDesc, and returns the
|
|
||||||
// signature and SignerInfo.
|
|
||||||
SignBlob(ctx context.Context, genDesc BlobDescriptorGenerator, opts SignerSignOptions) ([]byte, *signature.SignerInfo, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
// signerAnnotation facilitates return of manifest annotations by signers
|
// signerAnnotation facilitates return of manifest annotations by signers
|
||||||
type signerAnnotation interface {
|
type signerAnnotation interface {
|
||||||
// PluginAnnotations returns signature manifest annotations returned from
|
// PluginAnnotations returns signature manifest annotations returned from
|
||||||
|
@ -125,7 +85,7 @@ type signerAnnotation interface {
|
||||||
PluginAnnotations() map[string]string
|
PluginAnnotations() map[string]string
|
||||||
}
|
}
|
||||||
|
|
||||||
// SignOptions contains parameters for [notation.Sign].
|
// SignOptions contains parameters for notation.Sign.
|
||||||
type SignOptions struct {
|
type SignOptions struct {
|
||||||
SignerSignOptions
|
SignerSignOptions
|
||||||
|
|
||||||
|
@ -140,30 +100,13 @@ type SignOptions struct {
|
||||||
|
|
||||||
// Sign signs the OCI artifact and push the signature to the Repository.
|
// Sign signs the OCI artifact and push the signature to the Repository.
|
||||||
// The descriptor of the sign content is returned upon successful signing.
|
// The descriptor of the sign content is returned upon successful signing.
|
||||||
//
|
|
||||||
// Deprecated: use [SignOCI] instead.
|
|
||||||
func Sign(ctx context.Context, signer Signer, repo registry.Repository, signOpts SignOptions) (ocispec.Descriptor, error) {
|
func Sign(ctx context.Context, signer Signer, repo registry.Repository, signOpts SignOptions) (ocispec.Descriptor, error) {
|
||||||
artifactMenifestDesc, _, err := SignOCI(ctx, signer, repo, signOpts)
|
|
||||||
return artifactMenifestDesc, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// SignOCI signs the OCI artifact and push the signature to the Repository.
|
|
||||||
//
|
|
||||||
// Both artifact and signature manifest descriptors are returned upon successful
|
|
||||||
// signing.
|
|
||||||
//
|
|
||||||
// Note: If the error type is [remote.ReferrersError] and
|
|
||||||
// referrerError.IsReferrersIndexDelete() returns true, the signature is
|
|
||||||
// successfully pushed to the repository, but the referrers index deletion
|
|
||||||
// failed. In this case, the artifact and signature manifest descriptors are
|
|
||||||
// returned with the error.
|
|
||||||
func SignOCI(ctx context.Context, signer Signer, repo registry.Repository, signOpts SignOptions) (artifactManifestDesc, sigManifestDesc ocispec.Descriptor, err error) {
|
|
||||||
// sanity check
|
// sanity check
|
||||||
if err := validateSignArguments(signer, signOpts.SignerSignOptions); err != nil {
|
if err := validateSignArguments(signer, signOpts.SignerSignOptions); err != nil {
|
||||||
return ocispec.Descriptor{}, ocispec.Descriptor{}, err
|
return ocispec.Descriptor{}, err
|
||||||
}
|
}
|
||||||
if repo == nil {
|
if repo == nil {
|
||||||
return ocispec.Descriptor{}, ocispec.Descriptor{}, errors.New("repo cannot be nil")
|
return ocispec.Descriptor{}, errors.New("repo cannot be nil")
|
||||||
}
|
}
|
||||||
|
|
||||||
logger := log.GetLogger(ctx)
|
logger := log.GetLogger(ctx)
|
||||||
|
@ -172,76 +115,52 @@ func SignOCI(ctx context.Context, signer Signer, repo registry.Repository, signO
|
||||||
// artifactRef is a valid full reference
|
// artifactRef is a valid full reference
|
||||||
artifactRef = ref.Reference
|
artifactRef = ref.Reference
|
||||||
}
|
}
|
||||||
artifactManifestDesc, err = repo.Resolve(ctx, artifactRef)
|
targetDesc, err := repo.Resolve(ctx, artifactRef)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ocispec.Descriptor{}, ocispec.Descriptor{}, fmt.Errorf("failed to resolve reference: %w", err)
|
return ocispec.Descriptor{}, fmt.Errorf("failed to resolve reference: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// artifactRef is a tag or a digest, if it's a digest it has to match
|
// artifactRef is a tag or a digest, if it's a digest it has to match
|
||||||
// the resolved digest
|
// the resolved digest
|
||||||
if artifactRef != artifactManifestDesc.Digest.String() {
|
if artifactRef != targetDesc.Digest.String() {
|
||||||
if _, err := digest.Parse(artifactRef); err == nil {
|
if _, err := digest.Parse(artifactRef); err == nil {
|
||||||
// artifactRef is a digest, but does not match the resolved digest
|
// artifactRef is a digest, but does not match the resolved digest
|
||||||
return ocispec.Descriptor{}, ocispec.Descriptor{}, fmt.Errorf("user input digest %s does not match the resolved digest %s", artifactRef, artifactManifestDesc.Digest.String())
|
return ocispec.Descriptor{}, fmt.Errorf("user input digest %s does not match the resolved digest %s", artifactRef, targetDesc.Digest.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
// artifactRef is a tag
|
// artifactRef is a tag
|
||||||
logger.Warnf("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", artifactRef)
|
logger.Warnf("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", artifactRef)
|
||||||
logger.Infof("Resolved artifact tag `%s` to digest `%v` before signing", artifactRef, artifactManifestDesc.Digest)
|
logger.Infof("Resolved artifact tag `%s` to digest `%s` before signing", artifactRef, targetDesc.Digest.String())
|
||||||
}
|
}
|
||||||
descToSign, err := addUserMetadataToDescriptor(ctx, artifactManifestDesc, signOpts.UserMetadata)
|
descToSign, err := addUserMetadataToDescriptor(ctx, targetDesc, signOpts.UserMetadata)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ocispec.Descriptor{}, ocispec.Descriptor{}, err
|
return ocispec.Descriptor{}, err
|
||||||
}
|
}
|
||||||
sig, signerInfo, err := signer.Sign(ctx, descToSign, signOpts.SignerSignOptions)
|
sig, signerInfo, err := signer.Sign(ctx, descToSign, signOpts.SignerSignOptions)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ocispec.Descriptor{}, ocispec.Descriptor{}, err
|
return ocispec.Descriptor{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
var pluginAnnotations map[string]string
|
var pluginAnnotations map[string]string
|
||||||
if signerAnts, ok := signer.(signerAnnotation); ok {
|
if signerAnts, ok := signer.(signerAnnotation); ok {
|
||||||
pluginAnnotations = signerAnts.PluginAnnotations()
|
pluginAnnotations = signerAnts.PluginAnnotations()
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.Debug("Generating annotation")
|
logger.Debug("Generating annotation")
|
||||||
annotations, err := generateAnnotations(signerInfo, pluginAnnotations)
|
annotations, err := generateAnnotations(signerInfo, pluginAnnotations)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ocispec.Descriptor{}, ocispec.Descriptor{}, err
|
return ocispec.Descriptor{}, err
|
||||||
}
|
}
|
||||||
logger.Debugf("Generated annotations: %+v", annotations)
|
logger.Debugf("Generated annotations: %+v", annotations)
|
||||||
logger.Debugf("Pushing signature of artifact descriptor: %+v, signature media type: %v", artifactManifestDesc, signOpts.SignatureMediaType)
|
logger.Debugf("Pushing signature of artifact descriptor: %+v, signature media type: %v", targetDesc, signOpts.SignatureMediaType)
|
||||||
_, sigManifestDesc, err = repo.PushSignature(ctx, signOpts.SignatureMediaType, sig, artifactManifestDesc, annotations)
|
_, _, err = repo.PushSignature(ctx, signOpts.SignatureMediaType, sig, targetDesc, annotations)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
var referrerError *remote.ReferrersError
|
var referrerError *remote.ReferrersError
|
||||||
if errors.As(err, &referrerError) && referrerError.IsReferrersIndexDelete() {
|
// do not log an error for failing to delete referral index
|
||||||
// return the descriptors for referrersIndexDelete error as
|
if !errors.As(err, &referrerError) || !referrerError.IsReferrersIndexDelete() {
|
||||||
// the signature is successfully pushed to the repository
|
logger.Error("Failed to push the signature")
|
||||||
return artifactManifestDesc, sigManifestDesc, err
|
|
||||||
}
|
}
|
||||||
logger.Error("Failed to push the signature")
|
return ocispec.Descriptor{}, ErrorPushSignatureFailed{Msg: err.Error()}
|
||||||
return ocispec.Descriptor{}, ocispec.Descriptor{}, ErrorPushSignatureFailed{Msg: err.Error()}
|
|
||||||
}
|
|
||||||
return artifactManifestDesc, sigManifestDesc, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// SignBlob signs the arbitrary data from blobReader and returns
|
|
||||||
// the signature and SignerInfo.
|
|
||||||
func SignBlob(ctx context.Context, signer BlobSigner, blobReader io.Reader, signBlobOpts SignBlobOptions) ([]byte, *signature.SignerInfo, error) {
|
|
||||||
// sanity checks
|
|
||||||
if err := validateSignArguments(signer, signBlobOpts.SignerSignOptions); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
if blobReader == nil {
|
|
||||||
return nil, nil, errors.New("blobReader cannot be nil")
|
|
||||||
}
|
|
||||||
if signBlobOpts.ContentMediaType == "" {
|
|
||||||
return nil, nil, errors.New("content media-type cannot be empty")
|
|
||||||
}
|
|
||||||
if err := validateContentMediaType(signBlobOpts.ContentMediaType); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getDescFunc := getDescriptorFunc(ctx, blobReader, signBlobOpts.ContentMediaType, signBlobOpts.UserMetadata)
|
return targetDesc, nil
|
||||||
return signer.SignBlob(ctx, getDescFunc, signBlobOpts.SignerSignOptions)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func validateSignArguments(signer any, signOpts SignerSignOptions) error {
|
func validateSignArguments(signer any, signOpts SignerSignOptions) error {
|
||||||
|
@ -260,26 +179,33 @@ func validateSignArguments(signer any, signOpts SignerSignOptions) error {
|
||||||
if err := validateSigMediaType(signOpts.SignatureMediaType); err != nil {
|
if err := validateSigMediaType(signOpts.SignatureMediaType); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func addUserMetadataToDescriptor(ctx context.Context, desc ocispec.Descriptor, userMetadata map[string]string) (ocispec.Descriptor, error) {
|
func addUserMetadataToDescriptor(ctx context.Context, desc ocispec.Descriptor, userMetadata map[string]string) (ocispec.Descriptor, error) {
|
||||||
logger := log.GetLogger(ctx)
|
logger := log.GetLogger(ctx)
|
||||||
|
|
||||||
if desc.Annotations == nil && len(userMetadata) > 0 {
|
if desc.Annotations == nil && len(userMetadata) > 0 {
|
||||||
desc.Annotations = map[string]string{}
|
desc.Annotations = map[string]string{}
|
||||||
}
|
}
|
||||||
|
|
||||||
for k, v := range userMetadata {
|
for k, v := range userMetadata {
|
||||||
logger.Debugf("Adding metadata %v=%v to annotations", k, v)
|
logger.Debugf("Adding metadata %v=%v to annotations", k, v)
|
||||||
|
|
||||||
for _, reservedPrefix := range reservedAnnotationPrefixes {
|
for _, reservedPrefix := range reservedAnnotationPrefixes {
|
||||||
if strings.HasPrefix(k, reservedPrefix) {
|
if strings.HasPrefix(k, reservedPrefix) {
|
||||||
return desc, fmt.Errorf("error adding user metadata: metadata key %v has reserved prefix %v", k, reservedPrefix)
|
return desc, fmt.Errorf("error adding user metadata: metadata key %v has reserved prefix %v", k, reservedPrefix)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, ok := desc.Annotations[k]; ok {
|
if _, ok := desc.Annotations[k]; ok {
|
||||||
return desc, fmt.Errorf("error adding user metadata: metadata key %v is already present in the target artifact", k)
|
return desc, fmt.Errorf("error adding user metadata: metadata key %v is already present in the target artifact", k)
|
||||||
}
|
}
|
||||||
|
|
||||||
desc.Annotations[k] = v
|
desc.Annotations[k] = v
|
||||||
}
|
}
|
||||||
|
|
||||||
return desc, nil
|
return desc, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -321,7 +247,6 @@ type VerificationOutcome struct {
|
||||||
Error error
|
Error error
|
||||||
}
|
}
|
||||||
|
|
||||||
// UserMetadata returns the user metadata from the signature envelope.
|
|
||||||
func (outcome *VerificationOutcome) UserMetadata() (map[string]string, error) {
|
func (outcome *VerificationOutcome) UserMetadata() (map[string]string, error) {
|
||||||
if outcome.EnvelopeContent == nil {
|
if outcome.EnvelopeContent == nil {
|
||||||
return nil, errors.New("unable to find envelope content for verification outcome")
|
return nil, errors.New("unable to find envelope content for verification outcome")
|
||||||
|
@ -332,14 +257,15 @@ func (outcome *VerificationOutcome) UserMetadata() (map[string]string, error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.New("failed to unmarshal the payload content in the signature blob to envelope.Payload")
|
return nil, errors.New("failed to unmarshal the payload content in the signature blob to envelope.Payload")
|
||||||
}
|
}
|
||||||
|
|
||||||
if payload.TargetArtifact.Annotations == nil {
|
if payload.TargetArtifact.Annotations == nil {
|
||||||
return map[string]string{}, nil
|
return map[string]string{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return payload.TargetArtifact.Annotations, nil
|
return payload.TargetArtifact.Annotations, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// VerifierVerifyOptions contains parameters for [Verifier.Verify] used for
|
// VerifierVerifyOptions contains parameters for Verifier.Verify used for verifying OCI artifact.
|
||||||
// verifying OCI artifact.
|
|
||||||
type VerifierVerifyOptions struct {
|
type VerifierVerifyOptions struct {
|
||||||
// ArtifactReference is the reference of the artifact that is being
|
// ArtifactReference is the reference of the artifact that is being
|
||||||
// verified against to. It must be a full reference.
|
// verified against to. It must be a full reference.
|
||||||
|
@ -358,49 +284,22 @@ type VerifierVerifyOptions struct {
|
||||||
UserMetadata map[string]string
|
UserMetadata map[string]string
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verifier is a generic interface for verifying an OCI artifact.
|
// Verifier is a interface for verifying an OCI artifact.
|
||||||
type Verifier interface {
|
type Verifier interface {
|
||||||
// Verify verifies the `signature` associated with the target OCI artifact
|
// Verify verifies the `signature` associated with the target OCI artifact
|
||||||
// with manifest descriptor `desc`, and returns the outcome upon
|
//with manifest descriptor `desc`, and returns the outcome upon
|
||||||
// successful verification.
|
// successful verification.
|
||||||
// If nil signature is present and the verification level is not 'skip',
|
// If nil signature is present and the verification level is not 'skip',
|
||||||
// an error will be returned.
|
// an error will be returned.
|
||||||
Verify(ctx context.Context, desc ocispec.Descriptor, signature []byte, opts VerifierVerifyOptions) (*VerificationOutcome, error)
|
Verify(ctx context.Context, desc ocispec.Descriptor, signature []byte, opts VerifierVerifyOptions) (*VerificationOutcome, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// BlobVerifierVerifyOptions contains parameters for [BlobVerifier.Verify].
|
|
||||||
type BlobVerifierVerifyOptions struct {
|
|
||||||
// SignatureMediaType is the envelope type of the signature.
|
|
||||||
// Currently only `application/jose+json` and `application/cose` are
|
|
||||||
// supported.
|
|
||||||
SignatureMediaType string
|
|
||||||
|
|
||||||
// PluginConfig is a map of plugin configs.
|
|
||||||
PluginConfig map[string]string
|
|
||||||
|
|
||||||
// UserMetadata contains key-value pairs that must be present in the
|
|
||||||
// signature.
|
|
||||||
UserMetadata map[string]string
|
|
||||||
|
|
||||||
// TrustPolicyName is the name of trust policy picked by caller.
|
|
||||||
// If empty, the global trust policy will be applied.
|
|
||||||
TrustPolicyName string
|
|
||||||
}
|
|
||||||
|
|
||||||
// BlobVerifier is a generic interface for verifying a blob.
|
|
||||||
type BlobVerifier interface {
|
|
||||||
// VerifyBlob verifies the `signature` against the target blob using the
|
|
||||||
// descriptor returned by descGenFunc parameter and
|
|
||||||
// returns the outcome upon successful verification.
|
|
||||||
VerifyBlob(ctx context.Context, descGenFunc BlobDescriptorGenerator, signature []byte, opts BlobVerifierVerifyOptions) (*VerificationOutcome, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
type verifySkipper interface {
|
type verifySkipper interface {
|
||||||
// SkipVerify validates whether the verification level is skip.
|
// SkipVerify validates whether the verification level is skip.
|
||||||
SkipVerify(ctx context.Context, opts VerifierVerifyOptions) (bool, *trustpolicy.VerificationLevel, error)
|
SkipVerify(ctx context.Context, opts VerifierVerifyOptions) (bool, *trustpolicy.VerificationLevel, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// VerifyOptions contains parameters for [notation.Verify].
|
// VerifyOptions contains parameters for notation.Verify.
|
||||||
type VerifyOptions struct {
|
type VerifyOptions struct {
|
||||||
// ArtifactReference is the reference of the artifact that is being
|
// ArtifactReference is the reference of the artifact that is being
|
||||||
// verified against to.
|
// verified against to.
|
||||||
|
@ -419,51 +318,8 @@ type VerifyOptions struct {
|
||||||
UserMetadata map[string]string
|
UserMetadata map[string]string
|
||||||
}
|
}
|
||||||
|
|
||||||
// VerifyBlobOptions contains parameters for [notation.VerifyBlob].
|
|
||||||
type VerifyBlobOptions struct {
|
|
||||||
BlobVerifierVerifyOptions
|
|
||||||
|
|
||||||
// ContentMediaType is the media-type type of the content being verified.
|
|
||||||
ContentMediaType string
|
|
||||||
}
|
|
||||||
|
|
||||||
// VerifyBlob performs signature verification for a blob using notation supported
|
|
||||||
// verification types (like integrity, authenticity, etc.) and returns the
|
|
||||||
// successful signature verification outcome. The blob is read using blobReader,
|
|
||||||
// and upon successful verification, it returns the descriptor of the blob.
|
|
||||||
// For more details on signature verification, see
|
|
||||||
// https://github.com/notaryproject/notaryproject/blob/main/specs/trust-store-trust-policy.md#signature-verification
|
|
||||||
func VerifyBlob(ctx context.Context, blobVerifier BlobVerifier, blobReader io.Reader, signature []byte, verifyBlobOpts VerifyBlobOptions) (ocispec.Descriptor, *VerificationOutcome, error) {
|
|
||||||
if blobVerifier == nil {
|
|
||||||
return ocispec.Descriptor{}, nil, errors.New("blobVerifier cannot be nil")
|
|
||||||
}
|
|
||||||
if blobReader == nil {
|
|
||||||
return ocispec.Descriptor{}, nil, errors.New("blobReader cannot be nil")
|
|
||||||
}
|
|
||||||
if len(signature) == 0 {
|
|
||||||
return ocispec.Descriptor{}, nil, errors.New("signature cannot be nil or empty")
|
|
||||||
}
|
|
||||||
if err := validateContentMediaType(verifyBlobOpts.ContentMediaType); err != nil {
|
|
||||||
return ocispec.Descriptor{}, nil, err
|
|
||||||
}
|
|
||||||
if err := validateSigMediaType(verifyBlobOpts.SignatureMediaType); err != nil {
|
|
||||||
return ocispec.Descriptor{}, nil, err
|
|
||||||
}
|
|
||||||
getDescFunc := getDescriptorFunc(ctx, blobReader, verifyBlobOpts.ContentMediaType, verifyBlobOpts.UserMetadata)
|
|
||||||
vo, err := blobVerifier.VerifyBlob(ctx, getDescFunc, signature, verifyBlobOpts.BlobVerifierVerifyOptions)
|
|
||||||
if err != nil {
|
|
||||||
return ocispec.Descriptor{}, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var desc ocispec.Descriptor
|
|
||||||
if err = json.Unmarshal(vo.EnvelopeContent.Payload.Content, &desc); err != nil {
|
|
||||||
return ocispec.Descriptor{}, nil, err
|
|
||||||
}
|
|
||||||
return desc, vo, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Verify performs signature verification on each of the notation supported
|
// Verify performs signature verification on each of the notation supported
|
||||||
// verification types (like integrity, authenticity, etc.) and returns the
|
// verification types (like integrity, authenticity, etc.) and return the
|
||||||
// successful signature verification outcome.
|
// successful signature verification outcome.
|
||||||
// For more details on signature verification, see
|
// For more details on signature verification, see
|
||||||
// https://github.com/notaryproject/notaryproject/blob/main/specs/trust-store-trust-policy.md#signature-verification
|
// https://github.com/notaryproject/notaryproject/blob/main/specs/trust-store-trust-policy.md#signature-verification
|
||||||
|
@ -487,6 +343,7 @@ func Verify(ctx context.Context, verifier Verifier, repo registry.Repository, ve
|
||||||
PluginConfig: verifyOpts.PluginConfig,
|
PluginConfig: verifyOpts.PluginConfig,
|
||||||
UserMetadata: verifyOpts.UserMetadata,
|
UserMetadata: verifyOpts.UserMetadata,
|
||||||
}
|
}
|
||||||
|
|
||||||
if skipChecker, ok := verifier.(verifySkipper); ok {
|
if skipChecker, ok := verifier.(verifySkipper); ok {
|
||||||
logger.Info("Checking whether signature verification should be skipped or not")
|
logger.Info("Checking whether signature verification should be skipped or not")
|
||||||
skip, verificationLevel, err := skipChecker.SkipVerify(ctx, opts)
|
skip, verificationLevel, err := skipChecker.SkipVerify(ctx, opts)
|
||||||
|
@ -494,10 +351,10 @@ func Verify(ctx context.Context, verifier Verifier, repo registry.Repository, ve
|
||||||
return ocispec.Descriptor{}, nil, err
|
return ocispec.Descriptor{}, nil, err
|
||||||
}
|
}
|
||||||
if skip {
|
if skip {
|
||||||
logger.Infoln("Signature verification skipped for", verifyOpts.ArtifactReference)
|
logger.Infoln("Verification skipped for", verifyOpts.ArtifactReference)
|
||||||
return ocispec.Descriptor{}, []*VerificationOutcome{{VerificationLevel: verificationLevel}}, nil
|
return ocispec.Descriptor{}, []*VerificationOutcome{{VerificationLevel: verificationLevel}}, nil
|
||||||
}
|
}
|
||||||
logger.Info("Check over. The signature verification level is not set to 'skip' in the trust policy.")
|
logger.Info("Check over. Trust policy is not configured to skip signature verification")
|
||||||
}
|
}
|
||||||
|
|
||||||
// get artifact descriptor
|
// get artifact descriptor
|
||||||
|
@ -515,7 +372,7 @@ func Verify(ctx context.Context, verifier Verifier, repo registry.Repository, ve
|
||||||
}
|
}
|
||||||
if ref.ValidateReferenceAsDigest() != nil {
|
if ref.ValidateReferenceAsDigest() != nil {
|
||||||
// artifactRef is not a digest reference
|
// artifactRef is not a digest reference
|
||||||
logger.Infof("Resolved artifact tag `%s` to digest `%v` before verification", ref.Reference, artifactDescriptor.Digest)
|
logger.Infof("Resolved artifact tag `%s` to digest `%s` before verification", ref.Reference, artifactDescriptor.Digest.String())
|
||||||
logger.Warn("The resolved digest may not point to the same signed artifact, since tags are mutable")
|
logger.Warn("The resolved digest may not point to the same signed artifact, since tags are mutable")
|
||||||
} else if ref.Reference != artifactDescriptor.Digest.String() {
|
} else if ref.Reference != artifactDescriptor.Digest.String() {
|
||||||
return ocispec.Descriptor{}, nil, ErrorSignatureRetrievalFailed{Msg: fmt.Sprintf("user input digest %s does not match the resolved digest %s", ref.Reference, artifactDescriptor.Digest.String())}
|
return ocispec.Descriptor{}, nil, ErrorSignatureRetrievalFailed{Msg: fmt.Sprintf("user input digest %s does not match the resolved digest %s", ref.Reference, artifactDescriptor.Digest.String())}
|
||||||
|
@ -560,7 +417,6 @@ func Verify(ctx context.Context, verifier Verifier, repo registry.Repository, ve
|
||||||
}
|
}
|
||||||
// at this point, the signature is verified successfully
|
// at this point, the signature is verified successfully
|
||||||
verificationSucceeded = true
|
verificationSucceeded = true
|
||||||
|
|
||||||
// on success, verificationOutcomes only contains the
|
// on success, verificationOutcomes only contains the
|
||||||
// succeeded outcome
|
// succeeded outcome
|
||||||
verificationOutcomes = []*VerificationOutcome{outcome}
|
verificationOutcomes = []*VerificationOutcome{outcome}
|
||||||
|
@ -569,11 +425,14 @@ func Verify(ctx context.Context, verifier Verifier, repo registry.Repository, ve
|
||||||
// early break on success
|
// early break on success
|
||||||
return errDoneVerification
|
return errDoneVerification
|
||||||
}
|
}
|
||||||
|
|
||||||
if numOfSignatureProcessed >= verifyOpts.MaxSignatureAttempts {
|
if numOfSignatureProcessed >= verifyOpts.MaxSignatureAttempts {
|
||||||
return errExceededMaxVerificationLimit
|
return errExceededMaxVerificationLimit
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
|
|
||||||
if err != nil && !errors.Is(err, errDoneVerification) {
|
if err != nil && !errors.Is(err, errDoneVerification) {
|
||||||
if errors.Is(err, errExceededMaxVerificationLimit) {
|
if errors.Is(err, errExceededMaxVerificationLimit) {
|
||||||
return ocispec.Descriptor{}, verificationOutcomes, err
|
return ocispec.Descriptor{}, verificationOutcomes, err
|
||||||
|
@ -622,31 +481,6 @@ func generateAnnotations(signerInfo *signature.SignerInfo, annotations map[strin
|
||||||
return annotations, nil
|
return annotations, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func getDescriptorFunc(ctx context.Context, reader io.Reader, contentMediaType string, userMetadata map[string]string) BlobDescriptorGenerator {
|
|
||||||
return func(hashAlgo digest.Algorithm) (ocispec.Descriptor, error) {
|
|
||||||
digester := hashAlgo.Digester()
|
|
||||||
bytes, err := io.Copy(digester.Hash(), reader)
|
|
||||||
if err != nil {
|
|
||||||
return ocispec.Descriptor{}, err
|
|
||||||
}
|
|
||||||
targetDesc := ocispec.Descriptor{
|
|
||||||
MediaType: contentMediaType,
|
|
||||||
Digest: digester.Digest(),
|
|
||||||
Size: bytes,
|
|
||||||
}
|
|
||||||
return addUserMetadataToDescriptor(ctx, targetDesc, userMetadata)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func validateContentMediaType(contentMediaType string) error {
|
|
||||||
if contentMediaType != "" {
|
|
||||||
if _, _, err := mime.ParseMediaType(contentMediaType); err != nil {
|
|
||||||
return fmt.Errorf("invalid content media-type %q: %v", contentMediaType, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func validateSigMediaType(sigMediaType string) error {
|
func validateSigMediaType(sigMediaType string) error {
|
||||||
if !(sigMediaType == jws.MediaTypeEnvelope || sigMediaType == cose.MediaTypeEnvelope) {
|
if !(sigMediaType == jws.MediaTypeEnvelope || sigMediaType == cose.MediaTypeEnvelope) {
|
||||||
return fmt.Errorf("invalid signature media-type %q", sigMediaType)
|
return fmt.Errorf("invalid signature media-type %q", sigMediaType)
|
||||||
|
|
240
notation_test.go
240
notation_test.go
|
@ -18,11 +18,9 @@ import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
|
||||||
"math"
|
"math"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
@ -37,7 +35,6 @@ import (
|
||||||
"github.com/notaryproject/notation-go/plugin"
|
"github.com/notaryproject/notation-go/plugin"
|
||||||
"github.com/notaryproject/notation-go/registry"
|
"github.com/notaryproject/notation-go/registry"
|
||||||
"github.com/notaryproject/notation-go/verifier/trustpolicy"
|
"github.com/notaryproject/notation-go/verifier/trustpolicy"
|
||||||
"github.com/opencontainers/go-digest"
|
|
||||||
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -68,85 +65,6 @@ func TestSignSuccess(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSignBlobSuccess(t *testing.T) {
|
|
||||||
reader := strings.NewReader("some content")
|
|
||||||
testCases := []struct {
|
|
||||||
name string
|
|
||||||
dur time.Duration
|
|
||||||
mtype string
|
|
||||||
agent string
|
|
||||||
pConfig map[string]string
|
|
||||||
metadata map[string]string
|
|
||||||
}{
|
|
||||||
{"expiryInHours", 24 * time.Hour, "video/mp4", "", nil, nil},
|
|
||||||
{"oneSecondExpiry", 1 * time.Second, "video/mp4", "", nil, nil},
|
|
||||||
{"zeroExpiry", 0, "video/mp4", "", nil, nil},
|
|
||||||
{"validContentType", 1 * time.Second, "video/mp4", "", nil, nil},
|
|
||||||
{"emptyContentType", 1 * time.Second, "video/mp4", "someDummyAgent", map[string]string{"hi": "hello"}, map[string]string{"bye": "tata"}},
|
|
||||||
}
|
|
||||||
for _, tc := range testCases {
|
|
||||||
t.Run(tc.name, func(b *testing.T) {
|
|
||||||
opts := SignBlobOptions{
|
|
||||||
SignerSignOptions: SignerSignOptions{
|
|
||||||
SignatureMediaType: jws.MediaTypeEnvelope,
|
|
||||||
ExpiryDuration: tc.dur,
|
|
||||||
PluginConfig: tc.pConfig,
|
|
||||||
SigningAgent: tc.agent,
|
|
||||||
},
|
|
||||||
UserMetadata: expectedMetadata,
|
|
||||||
ContentMediaType: tc.mtype,
|
|
||||||
}
|
|
||||||
|
|
||||||
_, _, err := SignBlob(context.Background(), &dummySigner{}, reader, opts)
|
|
||||||
if err != nil {
|
|
||||||
b.Fatalf("Sign failed with error: %v", err)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSignBlobError(t *testing.T) {
|
|
||||||
reader := strings.NewReader("some content")
|
|
||||||
testCases := []struct {
|
|
||||||
name string
|
|
||||||
signer BlobSigner
|
|
||||||
dur time.Duration
|
|
||||||
rdr io.Reader
|
|
||||||
sigMType string
|
|
||||||
ctMType string
|
|
||||||
errMsg string
|
|
||||||
}{
|
|
||||||
{"negativeExpiry", &dummySigner{}, -1 * time.Second, nil, "video/mp4", jws.MediaTypeEnvelope, "expiry duration cannot be a negative value"},
|
|
||||||
{"milliSecExpiry", &dummySigner{}, 1 * time.Millisecond, nil, "video/mp4", jws.MediaTypeEnvelope, "expiry duration supports minimum granularity of seconds"},
|
|
||||||
{"invalidContentMediaType", &dummySigner{}, 1 * time.Second, reader, "video/mp4/zoping", jws.MediaTypeEnvelope, "invalid content media-type \"video/mp4/zoping\": mime: unexpected content after media subtype"},
|
|
||||||
{"emptyContentMediaType", &dummySigner{}, 1 * time.Second, reader, "", jws.MediaTypeEnvelope, "content media-type cannot be empty"},
|
|
||||||
{"invalidSignatureMediaType", &dummySigner{}, 1 * time.Second, reader, "", "", "content media-type cannot be empty"},
|
|
||||||
{"nilReader", &dummySigner{}, 1 * time.Second, nil, "video/mp4", jws.MediaTypeEnvelope, "blobReader cannot be nil"},
|
|
||||||
{"nilSigner", nil, 1 * time.Second, reader, "video/mp4", jws.MediaTypeEnvelope, "signer cannot be nil"},
|
|
||||||
{"signerError", &dummySigner{fail: true}, 1 * time.Second, reader, "video/mp4", jws.MediaTypeEnvelope, "expected SignBlob failure"},
|
|
||||||
}
|
|
||||||
for _, tc := range testCases {
|
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
|
||||||
opts := SignBlobOptions{
|
|
||||||
SignerSignOptions: SignerSignOptions{
|
|
||||||
SignatureMediaType: jws.MediaTypeEnvelope,
|
|
||||||
ExpiryDuration: tc.dur,
|
|
||||||
PluginConfig: nil,
|
|
||||||
},
|
|
||||||
ContentMediaType: tc.sigMType,
|
|
||||||
}
|
|
||||||
|
|
||||||
_, _, err := SignBlob(context.Background(), tc.signer, tc.rdr, opts)
|
|
||||||
if err == nil {
|
|
||||||
t.Fatalf("expected error but didnt found")
|
|
||||||
}
|
|
||||||
if err.Error() != tc.errMsg {
|
|
||||||
t.Fatalf("expected err message to be '%s' but found '%s'", tc.errMsg, err.Error())
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSignSuccessWithUserMetadata(t *testing.T) {
|
func TestSignSuccessWithUserMetadata(t *testing.T) {
|
||||||
repo := mock.NewRepository()
|
repo := mock.NewRepository()
|
||||||
opts := SignOptions{}
|
opts := SignOptions{}
|
||||||
|
@ -307,7 +225,7 @@ func TestSignOptsUnknownMediaType(t *testing.T) {
|
||||||
func TestRegistryResolveError(t *testing.T) {
|
func TestRegistryResolveError(t *testing.T) {
|
||||||
repo := mock.NewRepository()
|
repo := mock.NewRepository()
|
||||||
policyDocument := dummyPolicyDocument()
|
policyDocument := dummyPolicyDocument()
|
||||||
verifier := dummyVerifier{&policyDocument, mock.PluginManager{}, false, *trustpolicy.LevelStrict, false}
|
verifier := dummyVerifier{&policyDocument, mock.PluginManager{}, false, *trustpolicy.LevelStrict}
|
||||||
|
|
||||||
errorMessage := "network error"
|
errorMessage := "network error"
|
||||||
expectedErr := ErrorSignatureRetrievalFailed{Msg: errorMessage}
|
expectedErr := ErrorSignatureRetrievalFailed{Msg: errorMessage}
|
||||||
|
@ -325,7 +243,7 @@ func TestRegistryResolveError(t *testing.T) {
|
||||||
func TestVerifyEmptyReference(t *testing.T) {
|
func TestVerifyEmptyReference(t *testing.T) {
|
||||||
repo := mock.NewRepository()
|
repo := mock.NewRepository()
|
||||||
policyDocument := dummyPolicyDocument()
|
policyDocument := dummyPolicyDocument()
|
||||||
verifier := dummyVerifier{&policyDocument, mock.PluginManager{}, false, *trustpolicy.LevelStrict, false}
|
verifier := dummyVerifier{&policyDocument, mock.PluginManager{}, false, *trustpolicy.LevelStrict}
|
||||||
|
|
||||||
errorMessage := "reference is missing digest or tag"
|
errorMessage := "reference is missing digest or tag"
|
||||||
expectedErr := ErrorSignatureRetrievalFailed{Msg: errorMessage}
|
expectedErr := ErrorSignatureRetrievalFailed{Msg: errorMessage}
|
||||||
|
@ -341,7 +259,7 @@ func TestVerifyEmptyReference(t *testing.T) {
|
||||||
func TestVerifyTagReferenceFailed(t *testing.T) {
|
func TestVerifyTagReferenceFailed(t *testing.T) {
|
||||||
repo := mock.NewRepository()
|
repo := mock.NewRepository()
|
||||||
policyDocument := dummyPolicyDocument()
|
policyDocument := dummyPolicyDocument()
|
||||||
verifier := dummyVerifier{&policyDocument, mock.PluginManager{}, false, *trustpolicy.LevelStrict, false}
|
verifier := dummyVerifier{&policyDocument, mock.PluginManager{}, false, *trustpolicy.LevelStrict}
|
||||||
|
|
||||||
errorMessage := "invalid reference: invalid repository \"UPPERCASE/test\""
|
errorMessage := "invalid reference: invalid repository \"UPPERCASE/test\""
|
||||||
expectedErr := ErrorSignatureRetrievalFailed{Msg: errorMessage}
|
expectedErr := ErrorSignatureRetrievalFailed{Msg: errorMessage}
|
||||||
|
@ -358,7 +276,7 @@ func TestVerifyDigestNotMatchResolve(t *testing.T) {
|
||||||
repo := mock.NewRepository()
|
repo := mock.NewRepository()
|
||||||
repo.MissMatchDigest = true
|
repo.MissMatchDigest = true
|
||||||
policyDocument := dummyPolicyDocument()
|
policyDocument := dummyPolicyDocument()
|
||||||
verifier := dummyVerifier{&policyDocument, mock.PluginManager{}, false, *trustpolicy.LevelStrict, false}
|
verifier := dummyVerifier{&policyDocument, mock.PluginManager{}, false, *trustpolicy.LevelStrict}
|
||||||
|
|
||||||
errorMessage := fmt.Sprintf("user input digest %s does not match the resolved digest %s", mock.SampleDigest, mock.ZeroDigest)
|
errorMessage := fmt.Sprintf("user input digest %s does not match the resolved digest %s", mock.SampleDigest, mock.ZeroDigest)
|
||||||
expectedErr := ErrorSignatureRetrievalFailed{Msg: errorMessage}
|
expectedErr := ErrorSignatureRetrievalFailed{Msg: errorMessage}
|
||||||
|
@ -382,7 +300,7 @@ func TestSignDigestNotMatchResolve(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
errorMessage := fmt.Sprintf("user input digest %s does not match the resolved digest %s", mock.SampleDigest, mock.ZeroDigest)
|
errorMessage := fmt.Sprintf("user input digest %s does not match the resolved digest %s", mock.SampleDigest, mock.ZeroDigest)
|
||||||
expectedErr := errors.New(errorMessage)
|
expectedErr := fmt.Errorf(errorMessage)
|
||||||
|
|
||||||
_, err := Sign(context.Background(), &dummySigner{}, repo, signOpts)
|
_, err := Sign(context.Background(), &dummySigner{}, repo, signOpts)
|
||||||
if err == nil || err.Error() != errorMessage {
|
if err == nil || err.Error() != errorMessage {
|
||||||
|
@ -393,7 +311,7 @@ func TestSignDigestNotMatchResolve(t *testing.T) {
|
||||||
func TestSkippedSignatureVerification(t *testing.T) {
|
func TestSkippedSignatureVerification(t *testing.T) {
|
||||||
repo := mock.NewRepository()
|
repo := mock.NewRepository()
|
||||||
policyDocument := dummyPolicyDocument()
|
policyDocument := dummyPolicyDocument()
|
||||||
verifier := dummyVerifier{&policyDocument, mock.PluginManager{}, false, *trustpolicy.LevelSkip, false}
|
verifier := dummyVerifier{&policyDocument, mock.PluginManager{}, false, *trustpolicy.LevelSkip}
|
||||||
|
|
||||||
opts := VerifyOptions{ArtifactReference: mock.SampleArtifactUri, MaxSignatureAttempts: 50}
|
opts := VerifyOptions{ArtifactReference: mock.SampleArtifactUri, MaxSignatureAttempts: 50}
|
||||||
_, outcomes, err := Verify(context.Background(), &verifier, repo, opts)
|
_, outcomes, err := Verify(context.Background(), &verifier, repo, opts)
|
||||||
|
@ -406,7 +324,7 @@ func TestSkippedSignatureVerification(t *testing.T) {
|
||||||
func TestRegistryNoSignatureManifests(t *testing.T) {
|
func TestRegistryNoSignatureManifests(t *testing.T) {
|
||||||
repo := mock.NewRepository()
|
repo := mock.NewRepository()
|
||||||
policyDocument := dummyPolicyDocument()
|
policyDocument := dummyPolicyDocument()
|
||||||
verifier := dummyVerifier{&policyDocument, mock.PluginManager{}, false, *trustpolicy.LevelStrict, false}
|
verifier := dummyVerifier{&policyDocument, mock.PluginManager{}, false, *trustpolicy.LevelStrict}
|
||||||
errorMessage := fmt.Sprintf("no signature is associated with %q, make sure the artifact was signed successfully", mock.SampleArtifactUri)
|
errorMessage := fmt.Sprintf("no signature is associated with %q, make sure the artifact was signed successfully", mock.SampleArtifactUri)
|
||||||
expectedErr := ErrorSignatureRetrievalFailed{Msg: errorMessage}
|
expectedErr := ErrorSignatureRetrievalFailed{Msg: errorMessage}
|
||||||
|
|
||||||
|
@ -423,7 +341,7 @@ func TestRegistryNoSignatureManifests(t *testing.T) {
|
||||||
func TestRegistryFetchSignatureBlobError(t *testing.T) {
|
func TestRegistryFetchSignatureBlobError(t *testing.T) {
|
||||||
repo := mock.NewRepository()
|
repo := mock.NewRepository()
|
||||||
policyDocument := dummyPolicyDocument()
|
policyDocument := dummyPolicyDocument()
|
||||||
verifier := dummyVerifier{&policyDocument, mock.PluginManager{}, false, *trustpolicy.LevelStrict, false}
|
verifier := dummyVerifier{&policyDocument, mock.PluginManager{}, false, *trustpolicy.LevelStrict}
|
||||||
errorMessage := fmt.Sprintf("unable to retrieve digital signature with digest %q associated with %q from the Repository, error : network error", mock.SampleDigest, mock.SampleArtifactUri)
|
errorMessage := fmt.Sprintf("unable to retrieve digital signature with digest %q associated with %q from the Repository, error : network error", mock.SampleDigest, mock.SampleArtifactUri)
|
||||||
expectedErr := ErrorSignatureRetrievalFailed{Msg: errorMessage}
|
expectedErr := ErrorSignatureRetrievalFailed{Msg: errorMessage}
|
||||||
|
|
||||||
|
@ -440,35 +358,21 @@ func TestRegistryFetchSignatureBlobError(t *testing.T) {
|
||||||
func TestVerifyValid(t *testing.T) {
|
func TestVerifyValid(t *testing.T) {
|
||||||
repo := mock.NewRepository()
|
repo := mock.NewRepository()
|
||||||
policyDocument := dummyPolicyDocument()
|
policyDocument := dummyPolicyDocument()
|
||||||
verifier := dummyVerifier{&policyDocument, mock.PluginManager{}, false, *trustpolicy.LevelStrict, false}
|
verifier := dummyVerifier{&policyDocument, mock.PluginManager{}, false, *trustpolicy.LevelStrict}
|
||||||
|
|
||||||
// mock the repository
|
// mock the repository
|
||||||
opts := VerifyOptions{ArtifactReference: mock.SampleArtifactUri, MaxSignatureAttempts: 50}
|
opts := VerifyOptions{ArtifactReference: mock.SampleArtifactUri, MaxSignatureAttempts: 50}
|
||||||
_, _, err := Verify(context.Background(), &verifier, repo, opts)
|
_, _, err := Verify(context.Background(), &verifier, repo, opts)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("expected nil error, but got: %v", err)
|
t.Fatalf("SignaureMediaTypeMismatch expected: %v got: %v", nil, err)
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestVerifySkip(t *testing.T) {
|
|
||||||
repo := mock.NewRepository()
|
|
||||||
policyDocument := dummyPolicyDocument()
|
|
||||||
verifier := dummyVerifier{&policyDocument, mock.PluginManager{}, false, *trustpolicy.LevelStrict, true}
|
|
||||||
|
|
||||||
// mock the repository
|
|
||||||
opts := VerifyOptions{ArtifactReference: mock.SampleArtifactUri, MaxSignatureAttempts: 50}
|
|
||||||
_, _, err := Verify(context.Background(), &verifier, repo, opts)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("expected nil error, but got: %v", err)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMaxSignatureAttemptsMissing(t *testing.T) {
|
func TestMaxSignatureAttemptsMissing(t *testing.T) {
|
||||||
repo := mock.NewRepository()
|
repo := mock.NewRepository()
|
||||||
policyDocument := dummyPolicyDocument()
|
policyDocument := dummyPolicyDocument()
|
||||||
verifier := dummyVerifier{&policyDocument, mock.PluginManager{}, false, *trustpolicy.LevelStrict, false}
|
verifier := dummyVerifier{&policyDocument, mock.PluginManager{}, false, *trustpolicy.LevelStrict}
|
||||||
expectedErr := ErrorSignatureRetrievalFailed{Msg: fmt.Sprintf("verifyOptions.MaxSignatureAttempts expects a positive number, got %d", 0)}
|
expectedErr := ErrorSignatureRetrievalFailed{Msg: fmt.Sprintf("verifyOptions.MaxSignatureAttempts expects a positive number, got %d", 0)}
|
||||||
|
|
||||||
// mock the repository
|
// mock the repository
|
||||||
|
@ -484,7 +388,7 @@ func TestExceededMaxSignatureAttempts(t *testing.T) {
|
||||||
repo := mock.NewRepository()
|
repo := mock.NewRepository()
|
||||||
repo.ExceededNumOfSignatures = true
|
repo.ExceededNumOfSignatures = true
|
||||||
policyDocument := dummyPolicyDocument()
|
policyDocument := dummyPolicyDocument()
|
||||||
verifier := dummyVerifier{&policyDocument, mock.PluginManager{}, true, *trustpolicy.LevelStrict, false}
|
verifier := dummyVerifier{&policyDocument, mock.PluginManager{}, true, *trustpolicy.LevelStrict}
|
||||||
|
|
||||||
expectedErr := ErrorVerificationFailed{Msg: fmt.Sprintf("signature evaluation stopped. The configured limit of %d signatures to verify per artifact exceeded", 1)}
|
expectedErr := ErrorVerificationFailed{Msg: fmt.Sprintf("signature evaluation stopped. The configured limit of %d signatures to verify per artifact exceeded", 1)}
|
||||||
|
|
||||||
|
@ -501,7 +405,7 @@ func TestVerifyFailed(t *testing.T) {
|
||||||
t.Run("verification error", func(t *testing.T) {
|
t.Run("verification error", func(t *testing.T) {
|
||||||
policyDocument := dummyPolicyDocument()
|
policyDocument := dummyPolicyDocument()
|
||||||
repo := mock.NewRepository()
|
repo := mock.NewRepository()
|
||||||
verifier := dummyVerifier{&policyDocument, mock.PluginManager{}, true, *trustpolicy.LevelStrict, false}
|
verifier := dummyVerifier{&policyDocument, mock.PluginManager{}, true, *trustpolicy.LevelStrict}
|
||||||
expectedErr := ErrorVerificationFailed{}
|
expectedErr := ErrorVerificationFailed{}
|
||||||
|
|
||||||
// mock the repository
|
// mock the repository
|
||||||
|
@ -528,7 +432,7 @@ func TestVerifyFailed(t *testing.T) {
|
||||||
|
|
||||||
t.Run("repo is nil", func(t *testing.T) {
|
t.Run("repo is nil", func(t *testing.T) {
|
||||||
policyDocument := dummyPolicyDocument()
|
policyDocument := dummyPolicyDocument()
|
||||||
verifier := dummyVerifier{&policyDocument, mock.PluginManager{}, false, *trustpolicy.LevelStrict, false}
|
verifier := dummyVerifier{&policyDocument, mock.PluginManager{}, false, *trustpolicy.LevelStrict}
|
||||||
expectedErr := errors.New("repo cannot be nil")
|
expectedErr := errors.New("repo cannot be nil")
|
||||||
|
|
||||||
// mock the repository
|
// mock the repository
|
||||||
|
@ -541,73 +445,16 @@ func TestVerifyFailed(t *testing.T) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestVerifyBlobError(t *testing.T) {
|
func dummyPolicyDocument() (policyDoc trustpolicy.Document) {
|
||||||
reader := strings.NewReader("some content")
|
policyDoc = trustpolicy.Document{
|
||||||
sig := []byte("signature")
|
|
||||||
testCases := []struct {
|
|
||||||
name string
|
|
||||||
verifier BlobVerifier
|
|
||||||
sig []byte
|
|
||||||
rdr io.Reader
|
|
||||||
ctMType string
|
|
||||||
sigMType string
|
|
||||||
errMsg string
|
|
||||||
}{
|
|
||||||
{"nilVerifier", nil, sig, reader, "video/mp4", jws.MediaTypeEnvelope, "blobVerifier cannot be nil"},
|
|
||||||
{"verifierError", &dummyVerifier{FailVerify: true}, sig, reader, "video/mp4", jws.MediaTypeEnvelope, "failed verify"},
|
|
||||||
{"nilSignature", &dummyVerifier{}, nil, reader, "video/mp4", jws.MediaTypeEnvelope, "signature cannot be nil or empty"},
|
|
||||||
{"emptySignature", &dummyVerifier{}, []byte{}, reader, "video/mp4", jws.MediaTypeEnvelope, "signature cannot be nil or empty"},
|
|
||||||
{"nilReader", &dummyVerifier{}, sig, nil, "video/mp4", jws.MediaTypeEnvelope, "blobReader cannot be nil"},
|
|
||||||
{"invalidContentType", &dummyVerifier{}, sig, reader, "video/mp4/zoping", jws.MediaTypeEnvelope, "invalid content media-type \"video/mp4/zoping\": mime: unexpected content after media subtype"},
|
|
||||||
{"invalidSigType", &dummyVerifier{}, sig, reader, "video/mp4", "hola!", "invalid signature media-type \"hola!\""},
|
|
||||||
}
|
|
||||||
for _, tc := range testCases {
|
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
|
||||||
opts := VerifyBlobOptions{
|
|
||||||
BlobVerifierVerifyOptions: BlobVerifierVerifyOptions{
|
|
||||||
SignatureMediaType: tc.sigMType,
|
|
||||||
UserMetadata: nil,
|
|
||||||
TrustPolicyName: "",
|
|
||||||
},
|
|
||||||
ContentMediaType: tc.ctMType,
|
|
||||||
}
|
|
||||||
|
|
||||||
_, _, err := VerifyBlob(context.Background(), tc.verifier, tc.rdr, tc.sig, opts)
|
|
||||||
if err == nil {
|
|
||||||
t.Fatalf("expected error but didnt found")
|
|
||||||
}
|
|
||||||
if err.Error() != tc.errMsg {
|
|
||||||
t.Fatalf("expected err message to be '%s' but found '%s'", tc.errMsg, err.Error())
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestVerifyBlobValid(t *testing.T) {
|
|
||||||
opts := VerifyBlobOptions{
|
|
||||||
BlobVerifierVerifyOptions: BlobVerifierVerifyOptions{
|
|
||||||
SignatureMediaType: jws.MediaTypeEnvelope,
|
|
||||||
UserMetadata: nil,
|
|
||||||
TrustPolicyName: "",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
_, _, err := VerifyBlob(context.Background(), &dummyVerifier{}, strings.NewReader("some content"), []byte("signature"), opts)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("expected nil error, but got: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func dummyPolicyDocument() (policyDoc trustpolicy.OCIDocument) {
|
|
||||||
policyDoc = trustpolicy.OCIDocument{
|
|
||||||
Version: "1.0",
|
Version: "1.0",
|
||||||
TrustPolicies: []trustpolicy.OCITrustPolicy{dummyPolicyStatement()},
|
TrustPolicies: []trustpolicy.TrustPolicy{dummyPolicyStatement()},
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func dummyPolicyStatement() (policyStatement trustpolicy.OCITrustPolicy) {
|
func dummyPolicyStatement() (policyStatement trustpolicy.TrustPolicy) {
|
||||||
policyStatement = trustpolicy.OCITrustPolicy{
|
policyStatement = trustpolicy.TrustPolicy{
|
||||||
Name: "test-statement-name",
|
Name: "test-statement-name",
|
||||||
RegistryScopes: []string{"registry.acme-rockets.io/software/net-monitor"},
|
RegistryScopes: []string{"registry.acme-rockets.io/software/net-monitor"},
|
||||||
SignatureVerification: trustpolicy.SignatureVerification{VerificationLevel: "strict"},
|
SignatureVerification: trustpolicy.SignatureVerification{VerificationLevel: "strict"},
|
||||||
|
@ -617,9 +464,7 @@ func dummyPolicyStatement() (policyStatement trustpolicy.OCITrustPolicy) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
type dummySigner struct {
|
type dummySigner struct{}
|
||||||
fail bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *dummySigner) Sign(_ context.Context, _ ocispec.Descriptor, _ SignerSignOptions) ([]byte, *signature.SignerInfo, error) {
|
func (s *dummySigner) Sign(_ context.Context, _ ocispec.Descriptor, _ SignerSignOptions) ([]byte, *signature.SignerInfo, error) {
|
||||||
return []byte("ABC"), &signature.SignerInfo{
|
return []byte("ABC"), &signature.SignerInfo{
|
||||||
|
@ -629,23 +474,6 @@ func (s *dummySigner) Sign(_ context.Context, _ ocispec.Descriptor, _ SignerSign
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *dummySigner) SignBlob(_ context.Context, descGenFunc BlobDescriptorGenerator, _ SignerSignOptions) ([]byte, *signature.SignerInfo, error) {
|
|
||||||
if s.fail {
|
|
||||||
return nil, nil, errors.New("expected SignBlob failure")
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err := descGenFunc(digest.SHA384)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return []byte("ABC"), &signature.SignerInfo{
|
|
||||||
SignedAttributes: signature.SignedAttributes{
|
|
||||||
SigningTime: time.Now(),
|
|
||||||
},
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type verifyMetadataSigner struct{}
|
type verifyMetadataSigner struct{}
|
||||||
|
|
||||||
func (s *verifyMetadataSigner) Sign(_ context.Context, desc ocispec.Descriptor, _ SignerSignOptions) ([]byte, *signature.SignerInfo, error) {
|
func (s *verifyMetadataSigner) Sign(_ context.Context, desc ocispec.Descriptor, _ SignerSignOptions) ([]byte, *signature.SignerInfo, error) {
|
||||||
|
@ -662,11 +490,10 @@ func (s *verifyMetadataSigner) Sign(_ context.Context, desc ocispec.Descriptor,
|
||||||
}
|
}
|
||||||
|
|
||||||
type dummyVerifier struct {
|
type dummyVerifier struct {
|
||||||
TrustPolicyDoc *trustpolicy.OCIDocument
|
TrustPolicyDoc *trustpolicy.Document
|
||||||
PluginManager plugin.Manager
|
PluginManager plugin.Manager
|
||||||
FailVerify bool
|
FailVerify bool
|
||||||
VerificationLevel trustpolicy.VerificationLevel
|
VerificationLevel trustpolicy.VerificationLevel
|
||||||
SkipVerification bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *dummyVerifier) Verify(_ context.Context, _ ocispec.Descriptor, _ []byte, _ VerifierVerifyOptions) (*VerificationOutcome, error) {
|
func (v *dummyVerifier) Verify(_ context.Context, _ ocispec.Descriptor, _ []byte, _ VerifierVerifyOptions) (*VerificationOutcome, error) {
|
||||||
|
@ -680,29 +507,6 @@ func (v *dummyVerifier) Verify(_ context.Context, _ ocispec.Descriptor, _ []byte
|
||||||
return outcome, nil
|
return outcome, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *dummyVerifier) SkipVerify(_ context.Context, _ VerifierVerifyOptions) (bool, *trustpolicy.VerificationLevel, error) {
|
|
||||||
if v.SkipVerification {
|
|
||||||
return true, nil, nil
|
|
||||||
}
|
|
||||||
return false, nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v *dummyVerifier) VerifyBlob(_ context.Context, _ BlobDescriptorGenerator, _ []byte, _ BlobVerifierVerifyOptions) (*VerificationOutcome, error) {
|
|
||||||
if v.FailVerify {
|
|
||||||
return nil, errors.New("failed verify")
|
|
||||||
}
|
|
||||||
|
|
||||||
return &VerificationOutcome{
|
|
||||||
VerificationResults: []*ValidationResult{},
|
|
||||||
VerificationLevel: &v.VerificationLevel,
|
|
||||||
EnvelopeContent: &signature.EnvelopeContent{
|
|
||||||
Payload: signature.Payload{
|
|
||||||
Content: []byte("{}"),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
var (
|
||||||
reference = "sha256:19dbd2e48e921426ee8ace4dc892edfb2ecdc1d1a72d5416c83670c30acecef0"
|
reference = "sha256:19dbd2e48e921426ee8ace4dc892edfb2ecdc1d1a72d5416c83670c30acecef0"
|
||||||
artifactReference = "local/oci-layout@sha256:19dbd2e48e921426ee8ace4dc892edfb2ecdc1d1a72d5416c83670c30acecef0"
|
artifactReference = "local/oci-layout@sha256:19dbd2e48e921426ee8ace4dc892edfb2ecdc1d1a72d5416c83670c30acecef0"
|
||||||
|
@ -763,7 +567,7 @@ func TestLocalContent(t *testing.T) {
|
||||||
MaxSignatureAttempts: math.MaxInt64,
|
MaxSignatureAttempts: math.MaxInt64,
|
||||||
}
|
}
|
||||||
policyDocument := dummyPolicyDocument()
|
policyDocument := dummyPolicyDocument()
|
||||||
verifier := dummyVerifier{&policyDocument, mock.PluginManager{}, false, *trustpolicy.LevelStrict, false}
|
verifier := dummyVerifier{&policyDocument, mock.PluginManager{}, false, *trustpolicy.LevelStrict}
|
||||||
// verify signatures inside the OCI layout folder
|
// verify signatures inside the OCI layout folder
|
||||||
_, _, err = Verify(context.Background(), &verifier, repo, verifyOpts)
|
_, _, err = Verify(context.Background(), &verifier, repo, verifyOpts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -35,7 +35,7 @@ type Manager interface {
|
||||||
List(ctx context.Context) ([]string, error)
|
List(ctx context.Context) ([]string, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// CLIManager implements [Manager]
|
// CLIManager implements Manager
|
||||||
type CLIManager struct {
|
type CLIManager struct {
|
||||||
pluginFS dir.SysFS
|
pluginFS dir.SysFS
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,16 +27,12 @@ import (
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/notaryproject/notation-go/internal/io"
|
|
||||||
"github.com/notaryproject/notation-go/internal/slices"
|
"github.com/notaryproject/notation-go/internal/slices"
|
||||||
"github.com/notaryproject/notation-go/log"
|
"github.com/notaryproject/notation-go/log"
|
||||||
"github.com/notaryproject/notation-go/plugin/proto"
|
"github.com/notaryproject/notation-go/plugin/proto"
|
||||||
"github.com/notaryproject/notation-plugin-framework-go/plugin"
|
"github.com/notaryproject/notation-plugin-framework-go/plugin"
|
||||||
)
|
)
|
||||||
|
|
||||||
// maxPluginOutputSize is the maximum size of the plugin output.
|
|
||||||
const maxPluginOutputSize = 64 * 1024 * 1024 // 64 MiB
|
|
||||||
|
|
||||||
var executor commander = &execCommander{} // for unit test
|
var executor commander = &execCommander{} // for unit test
|
||||||
|
|
||||||
// GenericPlugin is the base requirement to be a plugin.
|
// GenericPlugin is the base requirement to be a plugin.
|
||||||
|
@ -63,7 +59,7 @@ type VerifyPlugin = plugin.VerifyPlugin
|
||||||
// To access Plugin, use the notation-plugin-framework-go's plugin.Plugin type.
|
// To access Plugin, use the notation-plugin-framework-go's plugin.Plugin type.
|
||||||
type Plugin = plugin.Plugin
|
type Plugin = plugin.Plugin
|
||||||
|
|
||||||
// CLIPlugin implements [Plugin] interface to CLI plugins.
|
// CLIPlugin implements Plugin interface to CLI plugins.
|
||||||
type CLIPlugin struct {
|
type CLIPlugin struct {
|
||||||
name string
|
name string
|
||||||
path string
|
path string
|
||||||
|
@ -226,15 +222,10 @@ func (c execCommander) Output(ctx context.Context, name string, command plugin.C
|
||||||
var stdout, stderr bytes.Buffer
|
var stdout, stderr bytes.Buffer
|
||||||
cmd := exec.CommandContext(ctx, name, string(command))
|
cmd := exec.CommandContext(ctx, name, string(command))
|
||||||
cmd.Stdin = bytes.NewReader(req)
|
cmd.Stdin = bytes.NewReader(req)
|
||||||
// The limit writer will be handled by the caller in run() by comparing the
|
cmd.Stderr = &stderr
|
||||||
// bytes written with the expected length of the bytes.
|
cmd.Stdout = &stdout
|
||||||
cmd.Stderr = io.LimitWriter(&stderr, maxPluginOutputSize)
|
|
||||||
cmd.Stdout = io.LimitWriter(&stdout, maxPluginOutputSize)
|
|
||||||
err := cmd.Run()
|
err := cmd.Run()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(ctx.Err(), context.DeadlineExceeded) {
|
|
||||||
return nil, stderr.Bytes(), fmt.Errorf("'%s %s' command execution timeout: %w", name, string(command), err)
|
|
||||||
}
|
|
||||||
return nil, stderr.Bytes(), err
|
return nil, stderr.Bytes(), err
|
||||||
}
|
}
|
||||||
return stdout.Bytes(), nil, nil
|
return stdout.Bytes(), nil, nil
|
||||||
|
|
|
@ -19,11 +19,9 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"os"
|
"os"
|
||||||
"reflect"
|
"reflect"
|
||||||
"runtime"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/notaryproject/notation-go/plugin/proto"
|
"github.com/notaryproject/notation-go/plugin/proto"
|
||||||
)
|
)
|
||||||
|
@ -183,7 +181,7 @@ func TestValidateMetadata(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNewCLIPlugin_Error(t *testing.T) {
|
func TestNewCLIPlugin_PathError(t *testing.T) {
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
t.Run("plugin directory exists without executable.", func(t *testing.T) {
|
t.Run("plugin directory exists without executable.", func(t *testing.T) {
|
||||||
p, err := NewCLIPlugin(ctx, "emptyplugin", "./testdata/plugins/emptyplugin/notation-emptyplugin")
|
p, err := NewCLIPlugin(ctx, "emptyplugin", "./testdata/plugins/emptyplugin/notation-emptyplugin")
|
||||||
|
@ -205,25 +203,6 @@ func TestNewCLIPlugin_Error(t *testing.T) {
|
||||||
t.Errorf("NewCLIPlugin() plugin = %v, want nil", p)
|
t.Errorf("NewCLIPlugin() plugin = %v, want nil", p)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("plugin timeout error", func(t *testing.T) {
|
|
||||||
if runtime.GOOS == "windows" {
|
|
||||||
t.Skip("skipping test on Windows")
|
|
||||||
}
|
|
||||||
expectedErrMsg := "'sleep 2' command execution timeout: signal: killed"
|
|
||||||
ctxWithTimout, cancel := context.WithTimeout(ctx, 10 * time.Millisecond)
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
var twoSeconds proto.Command
|
|
||||||
twoSeconds = "2"
|
|
||||||
_, _, err := execCommander{}.Output(ctxWithTimout, "sleep", twoSeconds, nil);
|
|
||||||
if err == nil {
|
|
||||||
t.Errorf("execCommander{}.Output() expected error = %v, got nil", expectedErrMsg)
|
|
||||||
}
|
|
||||||
if err.Error() != expectedErrMsg {
|
|
||||||
t.Errorf("execCommander{}.Output() error = %v, want %v", err, expectedErrMsg)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNewCLIPlugin_ValidError(t *testing.T) {
|
func TestNewCLIPlugin_ValidError(t *testing.T) {
|
||||||
|
|
|
@ -14,5 +14,5 @@
|
||||||
package registry
|
package registry
|
||||||
|
|
||||||
// ArtifactTypeNotation specifies the artifact type for a notation object.
|
// ArtifactTypeNotation specifies the artifact type for a notation object.
|
||||||
// spec: https://github.com/notaryproject/specifications/blob/v1.1.0/specs/signature-specification.md#signature
|
// spec: https://github.com/notaryproject/notaryproject/blob/efc828223710f99ab9639d2d0f72d59036a8e80c/specs/signature-specification.md#storage
|
||||||
const ArtifactTypeNotation = "application/vnd.cncf.notary.signature"
|
const ArtifactTypeNotation = "application/vnd.cncf.notary.signature"
|
||||||
|
|
|
@ -14,17 +14,19 @@
|
||||||
package registry
|
package registry
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/notaryproject/notation-go/log"
|
|
||||||
"github.com/notaryproject/notation-go/registry/internal/artifactspec"
|
"github.com/notaryproject/notation-go/registry/internal/artifactspec"
|
||||||
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||||
"oras.land/oras-go/v2"
|
"oras.land/oras-go/v2"
|
||||||
"oras.land/oras-go/v2/content"
|
"oras.land/oras-go/v2/content"
|
||||||
"oras.land/oras-go/v2/content/oci"
|
"oras.land/oras-go/v2/content/oci"
|
||||||
|
"oras.land/oras-go/v2/errdef"
|
||||||
"oras.land/oras-go/v2/registry"
|
"oras.land/oras-go/v2/registry"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -33,17 +35,30 @@ const (
|
||||||
maxManifestSizeLimit = 4 * 1024 * 1024 // 4 MiB
|
maxManifestSizeLimit = 4 * 1024 * 1024 // 4 MiB
|
||||||
)
|
)
|
||||||
|
|
||||||
// RepositoryOptions provides user options when creating a [Repository]
|
var (
|
||||||
|
// notationEmptyConfigDesc is the descriptor of an empty notation manifest
|
||||||
|
// config
|
||||||
|
// reference: https://github.com/notaryproject/specifications/blob/v1.0.0/specs/signature-specification.md#storage
|
||||||
|
notationEmptyConfigDesc = ocispec.Descriptor{
|
||||||
|
MediaType: ArtifactTypeNotation,
|
||||||
|
Digest: ocispec.DescriptorEmptyJSON.Digest,
|
||||||
|
Size: ocispec.DescriptorEmptyJSON.Size,
|
||||||
|
}
|
||||||
|
// notationEmptyConfigData is the data of an empty notation manifest config
|
||||||
|
notationEmptyConfigData = ocispec.DescriptorEmptyJSON.Data
|
||||||
|
)
|
||||||
|
|
||||||
|
// RepositoryOptions provides user options when creating a Repository
|
||||||
// it is kept for future extensibility
|
// it is kept for future extensibility
|
||||||
type RepositoryOptions struct{}
|
type RepositoryOptions struct{}
|
||||||
|
|
||||||
// repositoryClient implements [Repository]
|
// repositoryClient implements Repository
|
||||||
type repositoryClient struct {
|
type repositoryClient struct {
|
||||||
oras.GraphTarget
|
oras.GraphTarget
|
||||||
RepositoryOptions
|
RepositoryOptions
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewRepository returns a new [Repository].
|
// NewRepository returns a new Repository.
|
||||||
// Known implementations of oras.GraphTarget:
|
// Known implementations of oras.GraphTarget:
|
||||||
// - [remote.Repository](https://pkg.go.dev/oras.land/oras-go/v2/registry/remote#Repository)
|
// - [remote.Repository](https://pkg.go.dev/oras.land/oras-go/v2/registry/remote#Repository)
|
||||||
// - [oci.Store](https://pkg.go.dev/oras.land/oras-go/v2/content/oci#Store)
|
// - [oci.Store](https://pkg.go.dev/oras.land/oras-go/v2/content/oci#Store)
|
||||||
|
@ -53,7 +68,7 @@ func NewRepository(target oras.GraphTarget) Repository {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewRepositoryWithOptions returns a new [Repository] with user specified
|
// NewRepositoryWithOptions returns a new Repository with user specified
|
||||||
// options.
|
// options.
|
||||||
func NewRepositoryWithOptions(target oras.GraphTarget, opts RepositoryOptions) Repository {
|
func NewRepositoryWithOptions(target oras.GraphTarget, opts RepositoryOptions) Repository {
|
||||||
return &repositoryClient{
|
return &repositoryClient{
|
||||||
|
@ -62,7 +77,7 @@ func NewRepositoryWithOptions(target oras.GraphTarget, opts RepositoryOptions) R
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewOCIRepository returns a new [Repository] with oci.Store as
|
// NewOCIRepository returns a new Repository with oci.Store as
|
||||||
// its oras.GraphTarget. `path` denotes directory path to the target OCI layout.
|
// its oras.GraphTarget. `path` denotes directory path to the target OCI layout.
|
||||||
func NewOCIRepository(path string, opts RepositoryOptions) (Repository, error) {
|
func NewOCIRepository(path string, opts RepositoryOptions) (Repository, error) {
|
||||||
fileInfo, err := os.Stat(path)
|
fileInfo, err := os.Stat(path)
|
||||||
|
@ -93,6 +108,7 @@ func (c *repositoryClient) ListSignatures(ctx context.Context, desc ocispec.Desc
|
||||||
if repo, ok := c.GraphTarget.(registry.ReferrerLister); ok {
|
if repo, ok := c.GraphTarget.(registry.ReferrerLister); ok {
|
||||||
return repo.Referrers(ctx, desc, ArtifactTypeNotation, fn)
|
return repo.Referrers(ctx, desc, ArtifactTypeNotation, fn)
|
||||||
}
|
}
|
||||||
|
|
||||||
signatureManifests, err := signatureReferrers(ctx, c.GraphTarget, desc)
|
signatureManifests, err := signatureReferrers(ctx, c.GraphTarget, desc)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to get referrers during ListSignatures due to %w", err)
|
return fmt.Errorf("failed to get referrers during ListSignatures due to %w", err)
|
||||||
|
@ -110,6 +126,7 @@ func (c *repositoryClient) FetchSignatureBlob(ctx context.Context, desc ocispec.
|
||||||
if sigBlobDesc.Size > maxBlobSizeLimit {
|
if sigBlobDesc.Size > maxBlobSizeLimit {
|
||||||
return nil, ocispec.Descriptor{}, fmt.Errorf("signature blob too large: %d bytes", sigBlobDesc.Size)
|
return nil, ocispec.Descriptor{}, fmt.Errorf("signature blob too large: %d bytes", sigBlobDesc.Size)
|
||||||
}
|
}
|
||||||
|
|
||||||
var fetcher content.Fetcher = c.GraphTarget
|
var fetcher content.Fetcher = c.GraphTarget
|
||||||
if repo, ok := c.GraphTarget.(registry.Repository); ok {
|
if repo, ok := c.GraphTarget.(registry.Repository); ok {
|
||||||
fetcher = repo.Blobs()
|
fetcher = repo.Blobs()
|
||||||
|
@ -162,7 +179,6 @@ func (c *repositoryClient) getSignatureBlobDesc(ctx context.Context, sigManifest
|
||||||
|
|
||||||
// get the signature blob descriptor from signature manifest
|
// get the signature blob descriptor from signature manifest
|
||||||
var signatureBlobs []ocispec.Descriptor
|
var signatureBlobs []ocispec.Descriptor
|
||||||
|
|
||||||
// OCI image manifest
|
// OCI image manifest
|
||||||
if sigManifestDesc.MediaType == ocispec.MediaTypeImageManifest {
|
if sigManifestDesc.MediaType == ocispec.MediaTypeImageManifest {
|
||||||
var sigManifest ocispec.Manifest
|
var sigManifest ocispec.Manifest
|
||||||
|
@ -177,27 +193,55 @@ func (c *repositoryClient) getSignatureBlobDesc(ctx context.Context, sigManifest
|
||||||
}
|
}
|
||||||
signatureBlobs = sigManifest.Blobs
|
signatureBlobs = sigManifest.Blobs
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(signatureBlobs) != 1 {
|
if len(signatureBlobs) != 1 {
|
||||||
return ocispec.Descriptor{}, fmt.Errorf("signature manifest requries exactly one signature envelope blob, got %d", len(signatureBlobs))
|
return ocispec.Descriptor{}, fmt.Errorf("signature manifest requries exactly one signature envelope blob, got %d", len(signatureBlobs))
|
||||||
}
|
}
|
||||||
|
|
||||||
return signatureBlobs[0], nil
|
return signatureBlobs[0], nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// uploadSignatureManifest uploads the signature manifest to the registry
|
// uploadSignatureManifest uploads the signature manifest to the registry
|
||||||
func (c *repositoryClient) uploadSignatureManifest(ctx context.Context, subject, blobDesc ocispec.Descriptor, annotations map[string]string) (ocispec.Descriptor, error) {
|
func (c *repositoryClient) uploadSignatureManifest(ctx context.Context, subject, blobDesc ocispec.Descriptor, annotations map[string]string) (ocispec.Descriptor, error) {
|
||||||
|
configDesc, err := pushNotationManifestConfig(ctx, c.GraphTarget)
|
||||||
|
if err != nil {
|
||||||
|
return ocispec.Descriptor{}, fmt.Errorf("failed to push notation manifest config: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
opts := oras.PackManifestOptions{
|
opts := oras.PackManifestOptions{
|
||||||
Subject: &subject,
|
Subject: &subject,
|
||||||
ManifestAnnotations: annotations,
|
ManifestAnnotations: annotations,
|
||||||
Layers: []ocispec.Descriptor{blobDesc},
|
Layers: []ocispec.Descriptor{blobDesc},
|
||||||
|
ConfigDescriptor: &configDesc,
|
||||||
}
|
}
|
||||||
return oras.PackManifest(ctx, c.GraphTarget, oras.PackManifestVersion1_1, ArtifactTypeNotation, opts)
|
|
||||||
|
return oras.PackManifest(ctx, c.GraphTarget, oras.PackManifestVersion1_1, "", opts)
|
||||||
|
}
|
||||||
|
|
||||||
|
// pushNotationManifestConfig pushes an empty notation manifest config, if it
|
||||||
|
// doesn't exist.
|
||||||
|
//
|
||||||
|
// if the config exists, it returns the descriptor of the config without error.
|
||||||
|
func pushNotationManifestConfig(ctx context.Context, pusher content.Storage) (ocispec.Descriptor, error) {
|
||||||
|
// check if the config exists
|
||||||
|
exists, err := pusher.Exists(ctx, notationEmptyConfigDesc)
|
||||||
|
if err != nil {
|
||||||
|
return ocispec.Descriptor{}, fmt.Errorf("unable to verify existence: %s: %s. Details: %w", notationEmptyConfigDesc.Digest.String(), notationEmptyConfigDesc.MediaType, err)
|
||||||
|
}
|
||||||
|
if exists {
|
||||||
|
return notationEmptyConfigDesc, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// return nil if the config pushed successfully or it already exists
|
||||||
|
if err := pusher.Push(ctx, notationEmptyConfigDesc, bytes.NewReader(notationEmptyConfigData)); err != nil && !errors.Is(err, errdef.ErrAlreadyExists) {
|
||||||
|
return ocispec.Descriptor{}, fmt.Errorf("unable to push: %s: %s. Details: %w", notationEmptyConfigDesc.Digest.String(), notationEmptyConfigDesc.MediaType, err)
|
||||||
|
}
|
||||||
|
return notationEmptyConfigDesc, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// signatureReferrers returns referrer nodes of desc in target filtered by
|
// signatureReferrers returns referrer nodes of desc in target filtered by
|
||||||
// the "application/vnd.cncf.notary.signature" artifact type
|
// the "application/vnd.cncf.notary.signature" artifact type
|
||||||
func signatureReferrers(ctx context.Context, target content.ReadOnlyGraphStorage, desc ocispec.Descriptor) ([]ocispec.Descriptor, error) {
|
func signatureReferrers(ctx context.Context, target content.ReadOnlyGraphStorage, desc ocispec.Descriptor) ([]ocispec.Descriptor, error) {
|
||||||
logger := log.GetLogger(ctx)
|
|
||||||
|
|
||||||
var results []ocispec.Descriptor
|
var results []ocispec.Descriptor
|
||||||
predecessors, err := target.Predecessors(ctx, desc)
|
predecessors, err := target.Predecessors(ctx, desc)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -213,7 +257,6 @@ func signatureReferrers(ctx context.Context, target content.ReadOnlyGraphStorage
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
var artifact artifactspec.Artifact
|
var artifact artifactspec.Artifact
|
||||||
if err := json.Unmarshal(fetched, &artifact); err != nil {
|
if err := json.Unmarshal(fetched, &artifact); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -221,10 +264,6 @@ func signatureReferrers(ctx context.Context, target content.ReadOnlyGraphStorage
|
||||||
if artifact.Subject == nil || !content.Equal(*artifact.Subject, desc) {
|
if artifact.Subject == nil || !content.Equal(*artifact.Subject, desc) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if artifact.ArtifactType != ArtifactTypeNotation {
|
|
||||||
// not a valid Notary Project signature
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
node.ArtifactType = artifact.ArtifactType
|
node.ArtifactType = artifact.ArtifactType
|
||||||
node.Annotations = artifact.Annotations
|
node.Annotations = artifact.Annotations
|
||||||
case ocispec.MediaTypeImageManifest:
|
case ocispec.MediaTypeImageManifest:
|
||||||
|
@ -235,7 +274,6 @@ func signatureReferrers(ctx context.Context, target content.ReadOnlyGraphStorage
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
var image ocispec.Manifest
|
var image ocispec.Manifest
|
||||||
if err := json.Unmarshal(fetched, &image); err != nil {
|
if err := json.Unmarshal(fetched, &image); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -243,39 +281,15 @@ func signatureReferrers(ctx context.Context, target content.ReadOnlyGraphStorage
|
||||||
if image.Subject == nil || !content.Equal(*image.Subject, desc) {
|
if image.Subject == nil || !content.Equal(*image.Subject, desc) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
node.ArtifactType = image.Config.MediaType
|
||||||
// check if image is a valid Notary Project signature
|
|
||||||
switch image.ArtifactType {
|
|
||||||
case ArtifactTypeNotation:
|
|
||||||
// 1. artifactType is "application/vnd.cncf.notary.signature",
|
|
||||||
// and config.mediaType is "application/vnd.oci.empty.v1+json"
|
|
||||||
if image.Config.MediaType == ocispec.MediaTypeEmptyJSON {
|
|
||||||
node.ArtifactType = image.ArtifactType
|
|
||||||
} else {
|
|
||||||
// not a valid Notary Project signature
|
|
||||||
logger.Warnf("not a valid Notary Project signature with artifactType %q, but config.mediaType is %q", image.ArtifactType, image.Config.MediaType)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
case "":
|
|
||||||
// 2. artifacteType does not exist,
|
|
||||||
// and config.mediaType is "application/vnd.cncf.notary.signature"
|
|
||||||
if image.Config.MediaType == ArtifactTypeNotation {
|
|
||||||
node.ArtifactType = image.Config.MediaType
|
|
||||||
} else {
|
|
||||||
// not a valid Notary Project signature
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
// not a valid Notary Project signature
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
node.Annotations = image.Annotations
|
node.Annotations = image.Annotations
|
||||||
default:
|
default:
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
// only keep nodes of "application/vnd.cncf.notary.signature"
|
||||||
// add the node to results
|
if node.ArtifactType == ArtifactTypeNotation {
|
||||||
results = append(results, node)
|
results = append(results, node)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return results, nil
|
return results, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,7 +16,6 @@ package registry
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
@ -141,7 +140,7 @@ func (c mockRemoteClient) Do(req *http.Request) (*http.Response, error) {
|
||||||
Body: io.NopCloser(bytes.NewReader([]byte{})),
|
Body: io.NopCloser(bytes.NewReader([]byte{})),
|
||||||
}, nil
|
}, nil
|
||||||
case "/v2/test/manifests/" + invalidDigest:
|
case "/v2/test/manifests/" + invalidDigest:
|
||||||
return &http.Response{}, errors.New(errMsg)
|
return &http.Response{}, fmt.Errorf(errMsg)
|
||||||
case "v2/test/manifest/" + validDigest2:
|
case "v2/test/manifest/" + validDigest2:
|
||||||
return &http.Response{
|
return &http.Response{
|
||||||
StatusCode: http.StatusOK,
|
StatusCode: http.StatusOK,
|
||||||
|
@ -165,7 +164,7 @@ func (c mockRemoteClient) Do(req *http.Request) (*http.Response, error) {
|
||||||
},
|
},
|
||||||
}, nil
|
}, nil
|
||||||
default:
|
default:
|
||||||
return &http.Response{}, errors.New(msg)
|
return &http.Response{}, fmt.Errorf(msg)
|
||||||
}
|
}
|
||||||
case "/v2/test/referrers/":
|
case "/v2/test/referrers/":
|
||||||
return &http.Response{
|
return &http.Response{
|
||||||
|
@ -221,7 +220,7 @@ func (c mockRemoteClient) Do(req *http.Request) (*http.Response, error) {
|
||||||
}
|
}
|
||||||
return resp, nil
|
return resp, nil
|
||||||
}
|
}
|
||||||
return &http.Response{}, errors.New(errMsg)
|
return &http.Response{}, fmt.Errorf(errMsg)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -481,8 +480,8 @@ var (
|
||||||
}
|
}
|
||||||
expectedSignatureManifestDesc = ocispec.Descriptor{
|
expectedSignatureManifestDesc = ocispec.Descriptor{
|
||||||
MediaType: "application/vnd.oci.image.manifest.v1+json",
|
MediaType: "application/vnd.oci.image.manifest.v1+json",
|
||||||
Digest: "sha256:64300ad03f1dcd18136787363f3069c9598623221cbe76e3233d35266b7973d6",
|
Digest: "sha256:baeaea44f55c94499b7e082bd3c98ad5ec40fdf23ef89cdf4e5db6b83e4f18f5",
|
||||||
Size: 793,
|
Size: 728,
|
||||||
}
|
}
|
||||||
expectedSignatureBlobDesc = ocispec.Descriptor{
|
expectedSignatureBlobDesc = ocispec.Descriptor{
|
||||||
MediaType: joseTag,
|
MediaType: joseTag,
|
||||||
|
@ -811,31 +810,6 @@ func TestSignatureReferrers(t *testing.T) {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("artifact manifest with invalid artifactType", func(t *testing.T) {
|
|
||||||
sigManifest := `{"artifactType":"invalid", "subject":{"mediaType":"application/vnd.oci.artifact.manifest.v1+json","digest":"sha256:sha256:44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a","size":2}}`
|
|
||||||
sigManifestDesc := ocispec.Descriptor{
|
|
||||||
Digest: "sha256:835c3386406350fbddf5ee376b358bd20c6c423d6becbec166f83c533e4df5d6",
|
|
||||||
MediaType: "application/vnd.oci.artifact.manifest.v1+json",
|
|
||||||
Size: 198,
|
|
||||||
}
|
|
||||||
store := &testStorage{
|
|
||||||
store: &memory.Store{},
|
|
||||||
PredecessorsDesc: []ocispec.Descriptor{sigManifestDesc},
|
|
||||||
FetchContent: []byte(sigManifest),
|
|
||||||
}
|
|
||||||
descriptors, err := signatureReferrers(context.Background(), store, ocispec.Descriptor{
|
|
||||||
Digest: "sha256:sha256:44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a",
|
|
||||||
MediaType: "application/vnd.oci.artifact.manifest.v1+json",
|
|
||||||
Size: 2,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("failed to get referrers: %v", err)
|
|
||||||
}
|
|
||||||
if len(descriptors) != 0 {
|
|
||||||
t.Fatalf("expected to get no referrers, but got: %v", descriptors)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("no valid image manifest", func(t *testing.T) {
|
t.Run("no valid image manifest", func(t *testing.T) {
|
||||||
store := &testStorage{
|
store := &testStorage{
|
||||||
store: &memory.Store{},
|
store: &memory.Store{},
|
||||||
|
@ -851,6 +825,7 @@ func TestSignatureReferrers(t *testing.T) {
|
||||||
descriptors, err := signatureReferrers(context.Background(), store, ocispec.Descriptor{
|
descriptors, err := signatureReferrers(context.Background(), store, ocispec.Descriptor{
|
||||||
Digest: "sha256:44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a",
|
Digest: "sha256:44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a",
|
||||||
})
|
})
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("failed to get referrers: %v", err)
|
t.Fatalf("failed to get referrers: %v", err)
|
||||||
}
|
}
|
||||||
|
@ -858,172 +833,4 @@ func TestSignatureReferrers(t *testing.T) {
|
||||||
t.Fatalf("expected to get no referrers, but got: %v", descriptors)
|
t.Fatalf("expected to get no referrers, but got: %v", descriptors)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("image manifest with invalid mediaType", func(t *testing.T) {
|
|
||||||
sigManifest := `{}`
|
|
||||||
sigManifestDesc := ocispec.Descriptor{
|
|
||||||
Digest: "sha256:44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a",
|
|
||||||
MediaType: "invalid",
|
|
||||||
Size: 2,
|
|
||||||
}
|
|
||||||
store := &testStorage{
|
|
||||||
store: &memory.Store{},
|
|
||||||
PredecessorsDesc: []ocispec.Descriptor{sigManifestDesc},
|
|
||||||
FetchContent: []byte(sigManifest),
|
|
||||||
}
|
|
||||||
descriptors, err := signatureReferrers(context.Background(), store, ocispec.Descriptor{})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("failed to get referrers: %v", err)
|
|
||||||
}
|
|
||||||
if len(descriptors) != 0 {
|
|
||||||
t.Fatal("expected length of descriptors to be 0")
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("image manifest with valid artifactType and config.MediaType", func(t *testing.T) {
|
|
||||||
sigManifest := `{"artifactType":"application/vnd.cncf.notary.signature","config":{"mediaType":"application/vnd.oci.empty.v1+json"},"subject":{"mediaType":"application/vnd.oci.image.manifest.v1+json","digest":"sha256:sha256:44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a","size":2}}`
|
|
||||||
sigManifestDesc := ocispec.Descriptor{
|
|
||||||
Digest: "sha256:ad3ab7874c72d7bf5db0e55ce839b37ee71320bf7c18ac1a512600963f03c54d",
|
|
||||||
MediaType: "application/vnd.oci.image.manifest.v1+json",
|
|
||||||
Size: 283,
|
|
||||||
}
|
|
||||||
store := &testStorage{
|
|
||||||
store: &memory.Store{},
|
|
||||||
PredecessorsDesc: []ocispec.Descriptor{sigManifestDesc},
|
|
||||||
FetchContent: []byte(sigManifest),
|
|
||||||
}
|
|
||||||
descriptors, err := signatureReferrers(context.Background(), store, ocispec.Descriptor{
|
|
||||||
Digest: "sha256:sha256:44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a",
|
|
||||||
MediaType: "application/vnd.oci.image.manifest.v1+json",
|
|
||||||
Size: 2,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("failed to get referrers: %v", err)
|
|
||||||
}
|
|
||||||
if len(descriptors) != 1 {
|
|
||||||
t.Fatal("expected length of descriptors to be 1")
|
|
||||||
}
|
|
||||||
if !content.Equal(sigManifestDesc, descriptors[0]) {
|
|
||||||
t.Fatalf("expected %v, got: %v", sigManifestDesc, descriptors[0])
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("image manifest with valid artifactType but invalid config.MediaType", func(t *testing.T) {
|
|
||||||
sigManifest := `{"artifactType":"application/vnd.cncf.notary.signature","config":{"mediaType":"invalid"},"subject":{"mediaType":"application/vnd.oci.image.manifest.v1+json","digest":"sha256:sha256:44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a","size":2}}`
|
|
||||||
sigManifestDesc := ocispec.Descriptor{
|
|
||||||
Digest: "sha256:becfe1975b40352d0c7bd1337707a4c471fdcfa1ac380f2875fe8076a3bc3581",
|
|
||||||
MediaType: "application/vnd.oci.image.manifest.v1+json",
|
|
||||||
Size: 257,
|
|
||||||
}
|
|
||||||
store := &testStorage{
|
|
||||||
store: &memory.Store{},
|
|
||||||
PredecessorsDesc: []ocispec.Descriptor{sigManifestDesc},
|
|
||||||
FetchContent: []byte(sigManifest),
|
|
||||||
}
|
|
||||||
descriptors, err := signatureReferrers(context.Background(), store, ocispec.Descriptor{
|
|
||||||
Digest: "sha256:sha256:44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a",
|
|
||||||
MediaType: "application/vnd.oci.image.manifest.v1+json",
|
|
||||||
Size: 2,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("failed to get referrers: %v", err)
|
|
||||||
}
|
|
||||||
if len(descriptors) != 0 {
|
|
||||||
t.Fatal("expected length of descriptors to be 0")
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("image manifest with no artifactType and valid config.MediaType", func(t *testing.T) {
|
|
||||||
sigManifest := `{"config":{"mediaType":"application/vnd.cncf.notary.signature"},"subject":{"mediaType":"application/vnd.oci.image.manifest.v1+json","digest":"sha256:sha256:44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a","size":2}}`
|
|
||||||
sigManifestDesc := ocispec.Descriptor{
|
|
||||||
Digest: "sha256:0e0be61f687ba634dd772f6d3048101f78f22fabda64cc9600671cee41ab2d47",
|
|
||||||
MediaType: "application/vnd.oci.image.manifest.v1+json",
|
|
||||||
Size: 232,
|
|
||||||
}
|
|
||||||
store := &testStorage{
|
|
||||||
store: &memory.Store{},
|
|
||||||
PredecessorsDesc: []ocispec.Descriptor{sigManifestDesc},
|
|
||||||
FetchContent: []byte(sigManifest),
|
|
||||||
}
|
|
||||||
descriptors, err := signatureReferrers(context.Background(), store, ocispec.Descriptor{
|
|
||||||
Digest: "sha256:sha256:44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a",
|
|
||||||
MediaType: "application/vnd.oci.image.manifest.v1+json",
|
|
||||||
Size: 2,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("failed to get referrers: %v", err)
|
|
||||||
}
|
|
||||||
if len(descriptors) != 1 {
|
|
||||||
t.Fatal("expected length of descriptors to be 1")
|
|
||||||
}
|
|
||||||
if !content.Equal(sigManifestDesc, descriptors[0]) {
|
|
||||||
t.Fatalf("expected %v, got: %v", sigManifestDesc, descriptors[0])
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("image manifest with no artifactType and invalid config.MediaType", func(t *testing.T) {
|
|
||||||
sigManifest := `{"config":{"mediaType":"invalid"},"subject":{"mediaType":"application/vnd.oci.image.manifest.v1+json","digest":"sha256:sha256:44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a","size":2}}`
|
|
||||||
sigManifestDesc := ocispec.Descriptor{
|
|
||||||
Digest: "sha256:1580e4f590269bd40a33e902888429c9bbb250902f5a7eb50f04fbb8bd4dbab3",
|
|
||||||
MediaType: "application/vnd.oci.image.manifest.v1+json",
|
|
||||||
Size: 202,
|
|
||||||
}
|
|
||||||
store := &testStorage{
|
|
||||||
store: &memory.Store{},
|
|
||||||
PredecessorsDesc: []ocispec.Descriptor{sigManifestDesc},
|
|
||||||
FetchContent: []byte(sigManifest),
|
|
||||||
}
|
|
||||||
descriptors, err := signatureReferrers(context.Background(), store, ocispec.Descriptor{
|
|
||||||
Digest: "sha256:sha256:44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a",
|
|
||||||
MediaType: "application/vnd.oci.image.manifest.v1+json",
|
|
||||||
Size: 2,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("failed to get referrers: %v", err)
|
|
||||||
}
|
|
||||||
if len(descriptors) != 0 {
|
|
||||||
t.Fatal("expected length of descriptors to be 0")
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("image manifest with invalid artifactType", func(t *testing.T) {
|
|
||||||
sigManifest := `{"artifactType":"invalid","config":{"mediaType":"application/vnd.oci.empty.v1+json"},"subject":{"mediaType":"application/vnd.oci.image.manifest.v1+json","digest":"sha256:sha256:44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a","size":2}}`
|
|
||||||
sigManifestDesc := ocispec.Descriptor{
|
|
||||||
Digest: "sha256:d8c225cb4eca3e15fa2a44c9d302044e8c8683399939e26f417edb82f8b69cc3",
|
|
||||||
MediaType: "application/vnd.oci.image.manifest.v1+json",
|
|
||||||
Size: 253,
|
|
||||||
}
|
|
||||||
store := &testStorage{
|
|
||||||
store: &memory.Store{},
|
|
||||||
PredecessorsDesc: []ocispec.Descriptor{sigManifestDesc},
|
|
||||||
FetchContent: []byte(sigManifest),
|
|
||||||
}
|
|
||||||
descriptors, err := signatureReferrers(context.Background(), store, ocispec.Descriptor{
|
|
||||||
Digest: "sha256:sha256:44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a",
|
|
||||||
MediaType: "application/vnd.oci.image.manifest.v1+json",
|
|
||||||
Size: 2,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("failed to get referrers: %v", err)
|
|
||||||
}
|
|
||||||
if len(descriptors) != 0 {
|
|
||||||
t.Fatal("expected length of descriptors to be 0")
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestUploadSignatureManifest(t *testing.T) {
|
|
||||||
ref, err := registry.ParseReference(validReference)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("failed to parse reference")
|
|
||||||
}
|
|
||||||
client := newRepositoryClientWithImageManifest(mockRemoteClient{}, ref, false)
|
|
||||||
manifest, err := client.uploadSignatureManifest(context.Background(),
|
|
||||||
ocispec.Descriptor{}, ocispec.Descriptor{}, nil)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("failed to upload signature manifest: %v", err)
|
|
||||||
}
|
|
||||||
if manifest.ArtifactType != ArtifactTypeNotation {
|
|
||||||
t.Fatalf("expected artifact type: %s, got: %s", ArtifactTypeNotation, manifest.ArtifactType)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,7 +15,6 @@ package signer
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"crypto"
|
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
|
@ -30,13 +29,11 @@ import (
|
||||||
"github.com/notaryproject/notation-go/log"
|
"github.com/notaryproject/notation-go/log"
|
||||||
"github.com/notaryproject/notation-go/plugin/proto"
|
"github.com/notaryproject/notation-go/plugin/proto"
|
||||||
"github.com/notaryproject/notation-plugin-framework-go/plugin"
|
"github.com/notaryproject/notation-plugin-framework-go/plugin"
|
||||||
"github.com/opencontainers/go-digest"
|
|
||||||
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||||
)
|
)
|
||||||
|
|
||||||
// PluginSigner signs artifacts and generates signatures.
|
// PluginSigner signs artifacts and generates signatures.
|
||||||
//
|
// It implements notation.Signer
|
||||||
// It implements [notation.Signer] and [notation.BlobSigner].
|
|
||||||
type PluginSigner struct {
|
type PluginSigner struct {
|
||||||
plugin plugin.SignPlugin
|
plugin plugin.SignPlugin
|
||||||
keyID string
|
keyID string
|
||||||
|
@ -44,23 +41,17 @@ type PluginSigner struct {
|
||||||
manifestAnnotations map[string]string
|
manifestAnnotations map[string]string
|
||||||
}
|
}
|
||||||
|
|
||||||
var algorithms = map[crypto.Hash]digest.Algorithm{
|
// NewFromPlugin creates a notation.Signer that signs artifacts and generates
|
||||||
crypto.SHA256: digest.SHA256,
|
|
||||||
crypto.SHA384: digest.SHA384,
|
|
||||||
crypto.SHA512: digest.SHA512,
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewFromPlugin creates a [PluginSigner] that signs artifacts and generates
|
|
||||||
// signatures by delegating the one or more operations to the named plugin,
|
// signatures by delegating the one or more operations to the named plugin,
|
||||||
// as defined in https://github.com/notaryproject/notaryproject/blob/main/specs/plugin-extensibility.md#signing-interfaces.
|
// as defined in https://github.com/notaryproject/notaryproject/blob/main/specs/plugin-extensibility.md#signing-interfaces.
|
||||||
//
|
//
|
||||||
// Deprecated: NewFromPlugin function exists for historical compatibility and
|
// Deprecated: NewFromPlugin function exists for historical compatibility and should not be used.
|
||||||
// should not be used. To create [PluginSigner], use NewPluginSigner() function.
|
// To create PluginSigner, use NewPluginSigner() function.
|
||||||
func NewFromPlugin(plugin plugin.SignPlugin, keyID string, pluginConfig map[string]string) (notation.Signer, error) {
|
func NewFromPlugin(plugin plugin.SignPlugin, keyID string, pluginConfig map[string]string) (notation.Signer, error) {
|
||||||
return NewPluginSigner(plugin, keyID, pluginConfig)
|
return NewPluginSigner(plugin, keyID, pluginConfig)
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewPluginSigner creates a [PluginSigner] that signs artifacts and generates
|
// NewPluginSigner creates a notation.Signer that signs artifacts and generates
|
||||||
// signatures by delegating the one or more operations to the named plugin,
|
// signatures by delegating the one or more operations to the named plugin,
|
||||||
// as defined in https://github.com/notaryproject/notaryproject/blob/main/specs/plugin-extensibility.md#signing-interfaces.
|
// as defined in https://github.com/notaryproject/notaryproject/blob/main/specs/plugin-extensibility.md#signing-interfaces.
|
||||||
func NewPluginSigner(plugin plugin.SignPlugin, keyID string, pluginConfig map[string]string) (*PluginSigner, error) {
|
func NewPluginSigner(plugin plugin.SignPlugin, keyID string, pluginConfig map[string]string) (*PluginSigner, error) {
|
||||||
|
@ -70,6 +61,7 @@ func NewPluginSigner(plugin plugin.SignPlugin, keyID string, pluginConfig map[st
|
||||||
if keyID == "" {
|
if keyID == "" {
|
||||||
return nil, errors.New("keyID not specified")
|
return nil, errors.New("keyID not specified")
|
||||||
}
|
}
|
||||||
|
|
||||||
return &PluginSigner{
|
return &PluginSigner{
|
||||||
plugin: plugin,
|
plugin: plugin,
|
||||||
keyID: keyID,
|
keyID: keyID,
|
||||||
|
@ -83,21 +75,24 @@ func (s *PluginSigner) PluginAnnotations() map[string]string {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sign signs the artifact described by its descriptor and returns the
|
// Sign signs the artifact described by its descriptor and returns the
|
||||||
// signature and SignerInfo.
|
// marshalled envelope.
|
||||||
func (s *PluginSigner) Sign(ctx context.Context, desc ocispec.Descriptor, opts notation.SignerSignOptions) ([]byte, *signature.SignerInfo, error) {
|
func (s *PluginSigner) Sign(ctx context.Context, desc ocispec.Descriptor, opts notation.SignerSignOptions) ([]byte, *signature.SignerInfo, error) {
|
||||||
logger := log.GetLogger(ctx)
|
logger := log.GetLogger(ctx)
|
||||||
mergedConfig := s.mergeConfig(opts.PluginConfig)
|
mergedConfig := s.mergeConfig(opts.PluginConfig)
|
||||||
|
|
||||||
logger.Debug("Invoking plugin's get-plugin-metadata command")
|
logger.Debug("Invoking plugin's get-plugin-metadata command")
|
||||||
metadata, err := s.plugin.GetMetadata(ctx, &plugin.GetMetadataRequest{PluginConfig: mergedConfig})
|
metadata, err := s.plugin.GetMetadata(ctx, &plugin.GetMetadataRequest{PluginConfig: mergedConfig})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.Debugf("Using plugin %v with capabilities %v to sign oci artifact %v in signature media type %v", metadata.Name, metadata.Capabilities, desc.Digest, opts.SignatureMediaType)
|
logger.Debugf("Using plugin %v with capabilities %v to sign oci artifact %v in signature media type %v", metadata.Name, metadata.Capabilities, desc.Digest, opts.SignatureMediaType)
|
||||||
if metadata.HasCapability(plugin.CapabilitySignatureGenerator) {
|
if metadata.HasCapability(plugin.CapabilitySignatureGenerator) {
|
||||||
ks, err := s.getKeySpec(ctx, mergedConfig)
|
ks, err := s.getKeySpec(ctx, mergedConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, fmt.Errorf("failed to sign with the plugin %s: %w", metadata.Name, err)
|
return nil, nil, fmt.Errorf("failed to sign with the plugin %s: %w", metadata.Name, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
sig, signerInfo, err := s.generateSignature(ctx, desc, opts, ks, metadata, mergedConfig)
|
sig, signerInfo, err := s.generateSignature(ctx, desc, opts, ks, metadata, mergedConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, fmt.Errorf("failed to sign with the plugin %s: %w", metadata.Name, err)
|
return nil, nil, fmt.Errorf("failed to sign with the plugin %s: %w", metadata.Name, err)
|
||||||
|
@ -110,41 +105,10 @@ func (s *PluginSigner) Sign(ctx context.Context, desc ocispec.Descriptor, opts n
|
||||||
}
|
}
|
||||||
return sig, signerInfo, nil
|
return sig, signerInfo, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil, nil, fmt.Errorf("plugin does not have signing capabilities")
|
return nil, nil, fmt.Errorf("plugin does not have signing capabilities")
|
||||||
}
|
}
|
||||||
|
|
||||||
// SignBlob signs the descriptor returned by genDesc, and returns the
|
|
||||||
// signature and SignerInfo.
|
|
||||||
func (s *PluginSigner) SignBlob(ctx context.Context, descGenFunc notation.BlobDescriptorGenerator, opts notation.SignerSignOptions) ([]byte, *signature.SignerInfo, error) {
|
|
||||||
logger := log.GetLogger(ctx)
|
|
||||||
mergedConfig := s.mergeConfig(opts.PluginConfig)
|
|
||||||
logger.Debug("Invoking plugin's get-plugin-metadata command")
|
|
||||||
metadata, err := s.plugin.GetMetadata(ctx, &plugin.GetMetadataRequest{PluginConfig: mergedConfig})
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
// only support blob signing with the signature generator capability because
|
|
||||||
// the envelope generator capability is designed for OCI signing.
|
|
||||||
// A new capability may be added in the future for blob signing.
|
|
||||||
if !metadata.HasCapability(plugin.CapabilitySignatureGenerator) {
|
|
||||||
return nil, nil, fmt.Errorf("the plugin %q lacks the signature generator capability required for blob signing", metadata.Name)
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.Debug("Invoking plugin's describe-key command")
|
|
||||||
ks, err := s.getKeySpec(ctx, mergedConfig)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// get descriptor to sign
|
|
||||||
desc, err := getDescriptor(ks, descGenFunc)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
logger.Debugf("Using plugin %v with capabilities %v to sign blob using descriptor %+v", metadata.Name, metadata.Capabilities, desc)
|
|
||||||
return s.generateSignature(ctx, desc, opts, ks, metadata, mergedConfig)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *PluginSigner) getKeySpec(ctx context.Context, config map[string]string) (signature.KeySpec, error) {
|
func (s *PluginSigner) getKeySpec(ctx context.Context, config map[string]string) (signature.KeySpec, error) {
|
||||||
logger := log.GetLogger(ctx)
|
logger := log.GetLogger(ctx)
|
||||||
logger.Debug("Invoking plugin's describe-key command")
|
logger.Debug("Invoking plugin's describe-key command")
|
||||||
|
@ -152,9 +116,11 @@ func (s *PluginSigner) getKeySpec(ctx context.Context, config map[string]string)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return signature.KeySpec{}, err
|
return signature.KeySpec{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if s.keyID != descKeyResp.KeyID {
|
if s.keyID != descKeyResp.KeyID {
|
||||||
return signature.KeySpec{}, fmt.Errorf("keyID in describeKey response %q does not match request %q", descKeyResp.KeyID, s.keyID)
|
return signature.KeySpec{}, fmt.Errorf("keyID in describeKey response %q does not match request %q", descKeyResp.KeyID, s.keyID)
|
||||||
}
|
}
|
||||||
|
|
||||||
return proto.DecodeKeySpec(descKeyResp.KeySpec)
|
return proto.DecodeKeySpec(descKeyResp.KeySpec)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -170,6 +136,7 @@ func (s *PluginSigner) generateSignature(ctx context.Context, desc ocispec.Descr
|
||||||
keySpec: ks,
|
keySpec: ks,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
opts.SigningAgent = fmt.Sprintf("%s %s/%s", signingAgent, metadata.Name, metadata.Version)
|
opts.SigningAgent = fmt.Sprintf("%s %s/%s", signingAgent, metadata.Name, metadata.Version)
|
||||||
return genericSigner.Sign(ctx, desc, opts)
|
return genericSigner.Sign(ctx, desc, opts)
|
||||||
}
|
}
|
||||||
|
@ -182,7 +149,6 @@ func (s *PluginSigner) generateSignatureEnvelope(ctx context.Context, desc ocisp
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, fmt.Errorf("envelope payload can't be marshalled: %w", err)
|
return nil, nil, fmt.Errorf("envelope payload can't be marshalled: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Execute plugin sign command.
|
// Execute plugin sign command.
|
||||||
req := &plugin.GenerateEnvelopeRequest{
|
req := &plugin.GenerateEnvelopeRequest{
|
||||||
ContractVersion: plugin.ContractVersion,
|
ContractVersion: plugin.ContractVersion,
|
||||||
|
@ -205,11 +171,13 @@ func (s *PluginSigner) generateSignatureEnvelope(ctx context.Context, desc ocisp
|
||||||
resp.SignatureEnvelopeType, req.SignatureEnvelopeType,
|
resp.SignatureEnvelopeType, req.SignatureEnvelopeType,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.Debug("Verifying signature envelope generated by the plugin")
|
logger.Debug("Verifying signature envelope generated by the plugin")
|
||||||
sigEnv, err := signature.ParseEnvelope(opts.SignatureMediaType, resp.SignatureEnvelope)
|
sigEnv, err := signature.ParseEnvelope(opts.SignatureMediaType, resp.SignatureEnvelope)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
envContent, err := sigEnv.Verify()
|
envContent, err := sigEnv.Verify()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, fmt.Errorf("generated signature failed verification: %w", err)
|
return nil, nil, fmt.Errorf("generated signature failed verification: %w", err)
|
||||||
|
@ -217,29 +185,31 @@ func (s *PluginSigner) generateSignatureEnvelope(ctx context.Context, desc ocisp
|
||||||
if err := envelope.ValidatePayloadContentType(&envContent.Payload); err != nil {
|
if err := envelope.ValidatePayloadContentType(&envContent.Payload); err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
content := envContent.Payload.Content
|
content := envContent.Payload.Content
|
||||||
var signedPayload envelope.Payload
|
var signedPayload envelope.Payload
|
||||||
if err = json.Unmarshal(content, &signedPayload); err != nil {
|
if err = json.Unmarshal(content, &signedPayload); err != nil {
|
||||||
return nil, nil, fmt.Errorf("signed envelope payload can't be unmarshalled: %w", err)
|
return nil, nil, fmt.Errorf("signed envelope payload can't be unmarshalled: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !isPayloadDescriptorValid(desc, signedPayload.TargetArtifact) {
|
if !isPayloadDescriptorValid(desc, signedPayload.TargetArtifact) {
|
||||||
return nil, nil, fmt.Errorf("during signing descriptor subject has changed from %+v to %+v", desc, signedPayload.TargetArtifact)
|
return nil, nil, fmt.Errorf("during signing descriptor subject has changed from %+v to %+v", desc, signedPayload.TargetArtifact)
|
||||||
}
|
}
|
||||||
|
|
||||||
if unknownAttributes := areUnknownAttributesAdded(content); len(unknownAttributes) != 0 {
|
if unknownAttributes := areUnknownAttributesAdded(content); len(unknownAttributes) != 0 {
|
||||||
return nil, nil, fmt.Errorf("during signing, following unknown attributes were added to subject descriptor: %+q", unknownAttributes)
|
return nil, nil, fmt.Errorf("during signing, following unknown attributes were added to subject descriptor: %+q", unknownAttributes)
|
||||||
}
|
}
|
||||||
|
|
||||||
s.manifestAnnotations = resp.Annotations
|
s.manifestAnnotations = resp.Annotations
|
||||||
return resp.SignatureEnvelope, &envContent.SignerInfo, nil
|
return resp.SignatureEnvelope, &envContent.SignerInfo, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *PluginSigner) mergeConfig(config map[string]string) map[string]string {
|
func (s *PluginSigner) mergeConfig(config map[string]string) map[string]string {
|
||||||
c := make(map[string]string, len(s.pluginConfig)+len(config))
|
c := make(map[string]string, len(s.pluginConfig)+len(config))
|
||||||
|
|
||||||
// First clone s.PluginConfig.
|
// First clone s.PluginConfig.
|
||||||
for k, v := range s.pluginConfig {
|
for k, v := range s.pluginConfig {
|
||||||
c[k] = v
|
c[k] = v
|
||||||
}
|
}
|
||||||
|
|
||||||
// Then set or override entries from config.
|
// Then set or override entries from config.
|
||||||
for k, v := range config {
|
for k, v := range config {
|
||||||
c[k] = v
|
c[k] = v
|
||||||
|
@ -257,6 +227,7 @@ func (s *PluginSigner) describeKey(ctx context.Context, config map[string]string
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return resp, nil
|
return resp, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -266,7 +237,6 @@ func isDescriptorSubset(original, newDesc ocispec.Descriptor) bool {
|
||||||
if !content.Equal(original, newDesc) {
|
if !content.Equal(original, newDesc) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// Plugins may append additional annotations but not replace/override
|
// Plugins may append additional annotations but not replace/override
|
||||||
// existing.
|
// existing.
|
||||||
for k, v := range original.Annotations {
|
for k, v := range original.Annotations {
|
||||||
|
@ -284,7 +254,6 @@ func isPayloadDescriptorValid(originalDesc, newDesc ocispec.Descriptor) bool {
|
||||||
|
|
||||||
func areUnknownAttributesAdded(content []byte) []string {
|
func areUnknownAttributesAdded(content []byte) []string {
|
||||||
var targetArtifactMap map[string]interface{}
|
var targetArtifactMap map[string]interface{}
|
||||||
|
|
||||||
// Ignoring error because we already successfully unmarshalled before this
|
// Ignoring error because we already successfully unmarshalled before this
|
||||||
// point
|
// point
|
||||||
_ = json.Unmarshal(content, &targetArtifactMap)
|
_ = json.Unmarshal(content, &targetArtifactMap)
|
||||||
|
@ -341,10 +310,12 @@ func (s *pluginPrimitiveSigner) Sign(payload []byte) ([]byte, []*x509.Certificat
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
keySpecHash, err := proto.HashAlgorithmFromKeySpec(s.keySpec)
|
keySpecHash, err := proto.HashAlgorithmFromKeySpec(s.keySpec)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
req := &plugin.GenerateSignatureRequest{
|
req := &plugin.GenerateSignatureRequest{
|
||||||
ContractVersion: plugin.ContractVersion,
|
ContractVersion: plugin.ContractVersion,
|
||||||
KeyID: s.keyID,
|
KeyID: s.keyID,
|
||||||
|
@ -353,6 +324,7 @@ func (s *pluginPrimitiveSigner) Sign(payload []byte) ([]byte, []*x509.Certificat
|
||||||
Payload: payload,
|
Payload: payload,
|
||||||
PluginConfig: s.pluginConfig,
|
PluginConfig: s.pluginConfig,
|
||||||
}
|
}
|
||||||
|
|
||||||
resp, err := s.plugin.GenerateSignature(s.ctx, req)
|
resp, err := s.plugin.GenerateSignature(s.ctx, req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
|
|
|
@ -32,7 +32,6 @@ import (
|
||||||
"github.com/notaryproject/notation-go/internal/envelope"
|
"github.com/notaryproject/notation-go/internal/envelope"
|
||||||
"github.com/notaryproject/notation-go/plugin"
|
"github.com/notaryproject/notation-go/plugin"
|
||||||
"github.com/notaryproject/notation-go/plugin/proto"
|
"github.com/notaryproject/notation-go/plugin/proto"
|
||||||
"github.com/opencontainers/go-digest"
|
|
||||||
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -71,16 +70,6 @@ type mockPlugin struct {
|
||||||
keySpec signature.KeySpec
|
keySpec signature.KeySpec
|
||||||
}
|
}
|
||||||
|
|
||||||
func getDescriptorFunc(throwError bool) func(hashAlgo digest.Algorithm) (ocispec.Descriptor, error) {
|
|
||||||
return func(hashAlgo digest.Algorithm) (ocispec.Descriptor, error) {
|
|
||||||
if throwError {
|
|
||||||
return ocispec.Descriptor{}, errors.New("")
|
|
||||||
}
|
|
||||||
return validSignDescriptor, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func newMockPlugin(key crypto.PrivateKey, certs []*x509.Certificate, keySpec signature.KeySpec) *mockPlugin {
|
func newMockPlugin(key crypto.PrivateKey, certs []*x509.Certificate, keySpec signature.KeySpec) *mockPlugin {
|
||||||
return &mockPlugin{
|
return &mockPlugin{
|
||||||
key: key,
|
key: key,
|
||||||
|
@ -216,17 +205,6 @@ func (p *mockPlugin) GenerateEnvelope(ctx context.Context, req *proto.GenerateEn
|
||||||
return &proto.GenerateEnvelopeResponse{}, nil
|
return &proto.GenerateEnvelopeResponse{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPluginSignerImpl(t *testing.T) {
|
|
||||||
p := &PluginSigner{}
|
|
||||||
if _, ok := interface{}(p).(notation.Signer); !ok {
|
|
||||||
t.Fatal("PluginSigner does not implement notation.Signer")
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, ok := interface{}(p).(notation.BlobSigner); !ok {
|
|
||||||
t.Fatal("PluginSigner does not implement notation.BlobSigner")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestNewFromPluginFailed(t *testing.T) {
|
func TestNewFromPluginFailed(t *testing.T) {
|
||||||
tests := map[string]struct {
|
tests := map[string]struct {
|
||||||
pl plugin.SignPlugin
|
pl plugin.SignPlugin
|
||||||
|
@ -347,37 +325,6 @@ func TestPluginSigner_Sign_Valid(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPluginSigner_SignBlob_Valid(t *testing.T) {
|
|
||||||
for _, envelopeType := range signature.RegisteredEnvelopeTypes() {
|
|
||||||
for _, keyCert := range keyCertPairCollections {
|
|
||||||
t.Run(fmt.Sprintf("external plugin,envelopeType=%v_keySpec=%v", envelopeType, keyCert.keySpecName), func(t *testing.T) {
|
|
||||||
keySpec, _ := proto.DecodeKeySpec(proto.KeySpec(keyCert.keySpecName))
|
|
||||||
pluginSigner := PluginSigner{
|
|
||||||
plugin: newMockPlugin(keyCert.key, keyCert.certs, keySpec),
|
|
||||||
}
|
|
||||||
validSignOpts.SignatureMediaType = envelopeType
|
|
||||||
data, signerInfo, err := pluginSigner.SignBlob(context.Background(), getDescriptorFunc(false), validSignOpts)
|
|
||||||
basicSignTest(t, &pluginSigner, envelopeType, data, signerInfo, err)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestPluginSigner_SignBlob_Invalid(t *testing.T) {
|
|
||||||
t.Run("blob signing with generate envelope plugin should fail", func(t *testing.T) {
|
|
||||||
plugin := &mockPlugin{}
|
|
||||||
plugin.wantEnvelope = true
|
|
||||||
pluginSigner := PluginSigner{
|
|
||||||
plugin: plugin,
|
|
||||||
}
|
|
||||||
_, _, err := pluginSigner.SignBlob(context.Background(), getDescriptorFunc(false), validSignOpts)
|
|
||||||
expectedErrMsg := "the plugin \"testPlugin\" lacks the signature generator capability required for blob signing"
|
|
||||||
if err == nil || !strings.Contains(err.Error(), expectedErrMsg) {
|
|
||||||
t.Fatalf("expected error %q, got %v", expectedErrMsg, err)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestPluginSigner_SignEnvelope_RunFailed(t *testing.T) {
|
func TestPluginSigner_SignEnvelope_RunFailed(t *testing.T) {
|
||||||
for _, envelopeType := range signature.RegisteredEnvelopeTypes() {
|
for _, envelopeType := range signature.RegisteredEnvelopeTypes() {
|
||||||
t.Run(fmt.Sprintf("envelopeType=%v", envelopeType), func(t *testing.T) {
|
t.Run(fmt.Sprintf("envelopeType=%v", envelopeType), func(t *testing.T) {
|
||||||
|
|
|
@ -12,8 +12,8 @@
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
// Package signer provides notation signing functionality. It implements the
|
// Package signer provides notation signing functionality. It implements the
|
||||||
// [notation.Signer] and [notation.BlobSigner] interfaces by providing
|
// notation.Signer interface by providing builtinSigner for local signing and
|
||||||
// builtinSigner for local signing and [PluginSigner] for remote signing.
|
// PluginSigner for remote signing.
|
||||||
package signer
|
package signer
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
@ -34,24 +34,22 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
// signingAgent is the unprotected header field used by signature.
|
// signingAgent is the unprotected header field used by signature.
|
||||||
const signingAgent = "notation-go/1.3.0+unreleased"
|
const signingAgent = "notation-go/1.2.1"
|
||||||
|
|
||||||
// GenericSigner implements [notation.Signer] and [notation.BlobSigner].
|
// GenericSigner implements notation.Signer and embeds signature.Signer
|
||||||
// It embeds signature.Signer.
|
|
||||||
type GenericSigner struct {
|
type GenericSigner struct {
|
||||||
signer signature.Signer
|
signer signature.Signer
|
||||||
}
|
}
|
||||||
|
|
||||||
// New returns a [notation.Signer] given key and cert chain.
|
// New returns a builtinSigner given key and cert chain
|
||||||
//
|
//
|
||||||
// Deprecated: New function exists for historical compatibility and
|
// Deprecated: New function exists for historical compatibility and should not be used.
|
||||||
// should not be used. To create [GenericSigner],
|
// To create GenericSigner, use NewGenericSigner() function.
|
||||||
// use NewGenericSigner() function.
|
|
||||||
func New(key crypto.PrivateKey, certChain []*x509.Certificate) (notation.Signer, error) {
|
func New(key crypto.PrivateKey, certChain []*x509.Certificate) (notation.Signer, error) {
|
||||||
return NewGenericSigner(key, certChain)
|
return NewGenericSigner(key, certChain)
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewGenericSigner returns a builtinSigner given key and cert chain.
|
// NewGenericSigner returns a builtinSigner given key and cert chain
|
||||||
func NewGenericSigner(key crypto.PrivateKey, certChain []*x509.Certificate) (*GenericSigner, error) {
|
func NewGenericSigner(key crypto.PrivateKey, certChain []*x509.Certificate) (*GenericSigner, error) {
|
||||||
localSigner, err := signature.NewLocalSigner(certChain, key)
|
localSigner, err := signature.NewLocalSigner(certChain, key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -62,13 +60,12 @@ func NewGenericSigner(key crypto.PrivateKey, certChain []*x509.Certificate) (*Ge
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewFromFiles returns a [notation.Signer] given key and certChain paths.
|
// NewFromFiles returns a builtinSigner given key and certChain paths.
|
||||||
func NewFromFiles(keyPath, certChainPath string) (notation.Signer, error) {
|
func NewFromFiles(keyPath, certChainPath string) (notation.Signer, error) {
|
||||||
return NewGenericSignerFromFiles(keyPath, certChainPath)
|
return NewGenericSignerFromFiles(keyPath, certChainPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewGenericSignerFromFiles returns a builtinSigner given key and certChain
|
// NewGenericSignerFromFiles returns a builtinSigner given key and certChain paths.
|
||||||
// paths.
|
|
||||||
func NewGenericSignerFromFiles(keyPath, certChainPath string) (*GenericSigner, error) {
|
func NewGenericSignerFromFiles(keyPath, certChainPath string) (*GenericSigner, error) {
|
||||||
if keyPath == "" {
|
if keyPath == "" {
|
||||||
return nil, errors.New("key path not specified")
|
return nil, errors.New("key path not specified")
|
||||||
|
@ -100,7 +97,7 @@ func NewGenericSignerFromFiles(keyPath, certChainPath string) (*GenericSigner, e
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sign signs the artifact described by its descriptor and returns the
|
// Sign signs the artifact described by its descriptor and returns the
|
||||||
// signature and SignerInfo.
|
// marshalled envelope.
|
||||||
func (s *GenericSigner) Sign(ctx context.Context, desc ocispec.Descriptor, opts notation.SignerSignOptions) ([]byte, *signature.SignerInfo, error) {
|
func (s *GenericSigner) Sign(ctx context.Context, desc ocispec.Descriptor, opts notation.SignerSignOptions) ([]byte, *signature.SignerInfo, error) {
|
||||||
logger := log.GetLogger(ctx)
|
logger := log.GetLogger(ctx)
|
||||||
logger.Debugf("Generic signing for %v in signature media type %v", desc.Digest, opts.SignatureMediaType)
|
logger.Debugf("Generic signing for %v in signature media type %v", desc.Digest, opts.SignatureMediaType)
|
||||||
|
@ -110,6 +107,7 @@ func (s *GenericSigner) Sign(ctx context.Context, desc ocispec.Descriptor, opts
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, fmt.Errorf("envelope payload can't be marshalled: %w", err)
|
return nil, nil, fmt.Errorf("envelope payload can't be marshalled: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var signingAgentId string
|
var signingAgentId string
|
||||||
if opts.SigningAgent != "" {
|
if opts.SigningAgent != "" {
|
||||||
signingAgentId = opts.SigningAgent
|
signingAgentId = opts.SigningAgent
|
||||||
|
@ -127,13 +125,12 @@ func (s *GenericSigner) Sign(ctx context.Context, desc ocispec.Descriptor, opts
|
||||||
ContentType: envelope.MediaTypePayloadV1,
|
ContentType: envelope.MediaTypePayloadV1,
|
||||||
Content: payloadBytes,
|
Content: payloadBytes,
|
||||||
},
|
},
|
||||||
Signer: s.signer,
|
Signer: s.signer,
|
||||||
SigningTime: time.Now(),
|
SigningTime: time.Now(),
|
||||||
SigningScheme: signature.SigningSchemeX509,
|
SigningScheme: signature.SigningSchemeX509,
|
||||||
SigningAgent: signingAgentId,
|
SigningAgent: signingAgentId,
|
||||||
Timestamper: opts.Timestamper,
|
Timestamper: opts.Timestamper,
|
||||||
TSARootCAs: opts.TSARootCAs,
|
TSARootCAs: opts.TSARootCAs,
|
||||||
TSARevocationValidator: opts.TSARevocationValidator,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add expiry only if ExpiryDuration is not zero
|
// Add expiry only if ExpiryDuration is not zero
|
||||||
|
@ -147,12 +144,6 @@ func (s *GenericSigner) Sign(ctx context.Context, desc ocispec.Descriptor, opts
|
||||||
logger.Debugf(" Expiry: %v", signReq.Expiry)
|
logger.Debugf(" Expiry: %v", signReq.Expiry)
|
||||||
logger.Debugf(" SigningScheme: %v", signReq.SigningScheme)
|
logger.Debugf(" SigningScheme: %v", signReq.SigningScheme)
|
||||||
logger.Debugf(" SigningAgent: %v", signReq.SigningAgent)
|
logger.Debugf(" SigningAgent: %v", signReq.SigningAgent)
|
||||||
if signReq.Timestamper != nil {
|
|
||||||
logger.Debug("Enabled timestamping")
|
|
||||||
if signReq.TSARevocationValidator != nil {
|
|
||||||
logger.Debug("Enabled timestamping certificate chain revocation check")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add ctx to the SignRequest
|
// Add ctx to the SignRequest
|
||||||
signReq = signReq.WithContext(ctx)
|
signReq = signReq.WithContext(ctx)
|
||||||
|
@ -162,10 +153,12 @@ func (s *GenericSigner) Sign(ctx context.Context, desc ocispec.Descriptor, opts
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
sig, err := sigEnv.Sign(signReq)
|
sig, err := sigEnv.Sign(signReq)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
envContent, err := sigEnv.Verify()
|
envContent, err := sigEnv.Verify()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, fmt.Errorf("generated signature failed verification: %v", err)
|
return nil, nil, fmt.Errorf("generated signature failed verification: %v", err)
|
||||||
|
@ -175,27 +168,3 @@ func (s *GenericSigner) Sign(ctx context.Context, desc ocispec.Descriptor, opts
|
||||||
}
|
}
|
||||||
return sig, &envContent.SignerInfo, nil
|
return sig, &envContent.SignerInfo, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// SignBlob signs the descriptor returned by genDesc, and returns the
|
|
||||||
// signature and SignerInfo.
|
|
||||||
func (s *GenericSigner) SignBlob(ctx context.Context, genDesc notation.BlobDescriptorGenerator, opts notation.SignerSignOptions) ([]byte, *signature.SignerInfo, error) {
|
|
||||||
logger := log.GetLogger(ctx)
|
|
||||||
logger.Debugf("Generic blob signing for signature media type %s", opts.SignatureMediaType)
|
|
||||||
ks, err := s.signer.KeySpec()
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
desc, err := getDescriptor(ks, genDesc)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
return s.Sign(ctx, desc, opts)
|
|
||||||
}
|
|
||||||
|
|
||||||
func getDescriptor(ks signature.KeySpec, genDesc notation.BlobDescriptorGenerator) (ocispec.Descriptor, error) {
|
|
||||||
digestAlg, ok := algorithms[ks.SignatureAlgorithm().Hash()]
|
|
||||||
if !ok {
|
|
||||||
return ocispec.Descriptor{}, fmt.Errorf("unknown hashing algo %v", ks.SignatureAlgorithm().Hash())
|
|
||||||
}
|
|
||||||
return genDesc(digestAlg)
|
|
||||||
}
|
|
||||||
|
|
|
@ -30,8 +30,6 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/notaryproject/notation-core-go/revocation"
|
|
||||||
"github.com/notaryproject/notation-core-go/revocation/purpose"
|
|
||||||
"github.com/notaryproject/notation-core-go/signature"
|
"github.com/notaryproject/notation-core-go/signature"
|
||||||
_ "github.com/notaryproject/notation-core-go/signature/cose"
|
_ "github.com/notaryproject/notation-core-go/signature/cose"
|
||||||
_ "github.com/notaryproject/notation-core-go/signature/jws"
|
_ "github.com/notaryproject/notation-core-go/signature/jws"
|
||||||
|
@ -55,8 +53,7 @@ type keyCertPair struct {
|
||||||
|
|
||||||
var keyCertPairCollections []*keyCertPair
|
var keyCertPairCollections []*keyCertPair
|
||||||
|
|
||||||
// setUpKeyCertPairCollections setups all combinations of private key and
|
// setUpKeyCertPairCollections setups all combinations of private key and certificates.
|
||||||
// certificates.
|
|
||||||
func setUpKeyCertPairCollections() []*keyCertPair {
|
func setUpKeyCertPairCollections() []*keyCertPair {
|
||||||
// rsa
|
// rsa
|
||||||
var keyCertPairs []*keyCertPair
|
var keyCertPairs []*keyCertPair
|
||||||
|
@ -163,17 +160,6 @@ func testSignerFromFile(t *testing.T, keyCert *keyCertPair, envelopeType, dir st
|
||||||
basicVerification(t, sig, envelopeType, keyCert.certs[len(keyCert.certs)-1], nil)
|
basicVerification(t, sig, envelopeType, keyCert.certs[len(keyCert.certs)-1], nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGenericSignerImpl(t *testing.T) {
|
|
||||||
g := &GenericSigner{}
|
|
||||||
if _, ok := interface{}(g).(notation.Signer); !ok {
|
|
||||||
t.Fatal("GenericSigner does not implement notation.Signer")
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, ok := interface{}(g).(notation.BlobSigner); !ok {
|
|
||||||
t.Fatal("GenericSigner does not implement notation.BlobSigner")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestNewFromFiles(t *testing.T) {
|
func TestNewFromFiles(t *testing.T) {
|
||||||
// sign with key
|
// sign with key
|
||||||
dir := t.TempDir()
|
dir := t.TempDir()
|
||||||
|
@ -271,52 +257,6 @@ func TestSignWithTimestamping(t *testing.T) {
|
||||||
if err == nil || err.Error() != expectedErrMsg {
|
if err == nil || err.Error() != expectedErrMsg {
|
||||||
t.Fatalf("expected %s, but got %s", expectedErrMsg, err)
|
t.Fatalf("expected %s, but got %s", expectedErrMsg, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// timestamping with unknown authority
|
|
||||||
desc, sOpts = generateSigningContent()
|
|
||||||
sOpts.SignatureMediaType = envelopeType
|
|
||||||
sOpts.Timestamper, err = tspclient.NewHTTPTimestamper(nil, rfc3161URL)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
sOpts.TSARootCAs = x509.NewCertPool()
|
|
||||||
tsaRevocationValidator, err := revocation.NewWithOptions(revocation.Options{
|
|
||||||
CertChainPurpose: purpose.Timestamping,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
sOpts.TSARevocationValidator = tsaRevocationValidator
|
|
||||||
_, _, err = s.Sign(ctx, desc, sOpts)
|
|
||||||
expectedErrMsg = "timestamp: failed to verify signed token: cms verification failure: x509: certificate signed by unknown authority"
|
|
||||||
if err == nil || err.Error() != expectedErrMsg {
|
|
||||||
t.Fatalf("expected %s, but got %s", expectedErrMsg, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSignBlobWithCertChain(t *testing.T) {
|
|
||||||
// sign with key
|
|
||||||
for _, envelopeType := range signature.RegisteredEnvelopeTypes() {
|
|
||||||
for _, keyCert := range keyCertPairCollections {
|
|
||||||
t.Run(fmt.Sprintf("envelopeType=%v_keySpec=%v", envelopeType, keyCert.keySpecName), func(t *testing.T) {
|
|
||||||
s, err := NewGenericSigner(keyCert.key, keyCert.certs)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("NewSigner() error = %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
sOpts := notation.SignerSignOptions{
|
|
||||||
SignatureMediaType: envelopeType,
|
|
||||||
}
|
|
||||||
sig, _, err := s.SignBlob(context.Background(), getDescriptorFunc(false), sOpts)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Sign() error = %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// basic verification
|
|
||||||
basicVerification(t, sig, envelopeType, keyCert.certs[len(keyCert.certs)-1], nil)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSignWithoutExpiry(t *testing.T) {
|
func TestSignWithoutExpiry(t *testing.T) {
|
||||||
|
|
|
@ -1,171 +0,0 @@
|
||||||
// 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 crl provides functionalities for crl revocation check.
|
|
||||||
package crl
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"crypto/sha256"
|
|
||||||
"crypto/x509"
|
|
||||||
"encoding/hex"
|
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"io/fs"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
corecrl "github.com/notaryproject/notation-core-go/revocation/crl"
|
|
||||||
"github.com/notaryproject/notation-go/internal/file"
|
|
||||||
"github.com/notaryproject/notation-go/log"
|
|
||||||
)
|
|
||||||
|
|
||||||
// FileCache implements corecrl.Cache.
|
|
||||||
//
|
|
||||||
// Key: url of the CRL.
|
|
||||||
//
|
|
||||||
// Value: corecrl.Bundle.
|
|
||||||
//
|
|
||||||
// This cache builds on top of the UNIX file system to leverage the file system's
|
|
||||||
// atomic operations. The `rename` and `remove` operations will unlink the old
|
|
||||||
// file but keep the inode and file descriptor for existing processes to access
|
|
||||||
// the file. The old inode will be dereferenced when all processes close the old
|
|
||||||
// file descriptor. Additionally, the operations are proven to be atomic on
|
|
||||||
// UNIX-like platforms, so there is no need to handle file locking.
|
|
||||||
//
|
|
||||||
// NOTE: For Windows, the `open`, `rename` and `remove` operations need file
|
|
||||||
// locking to ensure atomicity. The current implementation does not handle
|
|
||||||
// file locking, so the concurrent write from multiple processes may be failed.
|
|
||||||
// Please do not use this cache in a multi-process environment on Windows.
|
|
||||||
type FileCache struct {
|
|
||||||
// root is the root directory of the cache
|
|
||||||
root string
|
|
||||||
}
|
|
||||||
|
|
||||||
// fileCacheContent is the actual content saved in a FileCache
|
|
||||||
type fileCacheContent struct {
|
|
||||||
// BaseCRL is the ASN.1 encoded base CRL
|
|
||||||
BaseCRL []byte `json:"baseCRL"`
|
|
||||||
|
|
||||||
// DeltaCRL is the ASN.1 encoded delta CRL
|
|
||||||
DeltaCRL []byte `json:"deltaCRL,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewFileCache creates a FileCache with root as the root directory
|
|
||||||
//
|
|
||||||
// An example for root is `dir.CacheFS().SysPath(dir.PathCRLCache)`
|
|
||||||
func NewFileCache(root string) (*FileCache, error) {
|
|
||||||
if err := os.MkdirAll(root, 0700); err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to create crl file cache: %w", err)
|
|
||||||
}
|
|
||||||
return &FileCache{
|
|
||||||
root: root,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get retrieves CRL bundle from c given url as key. If the key does not exist
|
|
||||||
// or the content has expired, corecrl.ErrCacheMiss is returned.
|
|
||||||
func (c *FileCache) Get(ctx context.Context, url string) (*corecrl.Bundle, error) {
|
|
||||||
logger := log.GetLogger(ctx)
|
|
||||||
logger.Debugf("Retrieving crl bundle from file cache with key %q ...", url)
|
|
||||||
|
|
||||||
// get content from file cache
|
|
||||||
contentBytes, err := os.ReadFile(filepath.Join(c.root, c.fileName(url)))
|
|
||||||
if err != nil {
|
|
||||||
if errors.Is(err, fs.ErrNotExist) {
|
|
||||||
logger.Debugf("CRL file cache miss. Key %q does not exist", url)
|
|
||||||
return nil, corecrl.ErrCacheMiss
|
|
||||||
}
|
|
||||||
return nil, fmt.Errorf("failed to get crl bundle from file cache with key %q: %w", url, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// decode content to crl Bundle
|
|
||||||
var content fileCacheContent
|
|
||||||
if err := json.Unmarshal(contentBytes, &content); err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to decode file retrieved from file cache: %w", err)
|
|
||||||
}
|
|
||||||
var bundle corecrl.Bundle
|
|
||||||
bundle.BaseCRL, err = x509.ParseRevocationList(content.BaseCRL)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to parse base CRL of file retrieved from file cache: %w", err)
|
|
||||||
}
|
|
||||||
if content.DeltaCRL != nil {
|
|
||||||
bundle.DeltaCRL, err = x509.ParseRevocationList(content.DeltaCRL)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to parse delta CRL of file retrieved from file cache: %w", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// check expiry
|
|
||||||
if err := checkExpiry(ctx, bundle.BaseCRL.NextUpdate); err != nil {
|
|
||||||
return nil, fmt.Errorf("check BaseCRL expiry failed: %w", err)
|
|
||||||
}
|
|
||||||
if bundle.DeltaCRL != nil {
|
|
||||||
if err := checkExpiry(ctx, bundle.DeltaCRL.NextUpdate); err != nil {
|
|
||||||
return nil, fmt.Errorf("check DeltaCRL expiry failed: %w", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return &bundle, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set stores the CRL bundle in c with url as key.
|
|
||||||
func (c *FileCache) Set(ctx context.Context, url string, bundle *corecrl.Bundle) error {
|
|
||||||
logger := log.GetLogger(ctx)
|
|
||||||
logger.Debugf("Storing crl bundle to file cache with key %q ...", url)
|
|
||||||
|
|
||||||
if bundle == nil {
|
|
||||||
return errors.New("failed to store crl bundle in file cache: bundle cannot be nil")
|
|
||||||
}
|
|
||||||
if bundle.BaseCRL == nil {
|
|
||||||
return errors.New("failed to store crl bundle in file cache: bundle BaseCRL cannot be nil")
|
|
||||||
}
|
|
||||||
|
|
||||||
// actual content to be saved in the cache
|
|
||||||
content := fileCacheContent{
|
|
||||||
BaseCRL: bundle.BaseCRL.Raw,
|
|
||||||
}
|
|
||||||
if bundle.DeltaCRL != nil {
|
|
||||||
content.DeltaCRL = bundle.DeltaCRL.Raw
|
|
||||||
}
|
|
||||||
contentBytes, err := json.Marshal(content)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to store crl bundle in file cache: %w", err)
|
|
||||||
}
|
|
||||||
if err := file.WriteFile(c.root, filepath.Join(c.root, c.fileName(url)), contentBytes); err != nil {
|
|
||||||
return fmt.Errorf("failed to store crl bundle in file cache: %w", err)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// fileName returns the filename of the content stored in c
|
|
||||||
func (c *FileCache) fileName(url string) string {
|
|
||||||
hash := sha256.Sum256([]byte(url))
|
|
||||||
return hex.EncodeToString(hash[:])
|
|
||||||
}
|
|
||||||
|
|
||||||
// checkExpiry returns nil when nextUpdate is bounded before current time
|
|
||||||
func checkExpiry(ctx context.Context, nextUpdate time.Time) error {
|
|
||||||
logger := log.GetLogger(ctx)
|
|
||||||
|
|
||||||
if nextUpdate.IsZero() {
|
|
||||||
return errors.New("crl bundle retrieved from file cache does not contain valid NextUpdate")
|
|
||||||
}
|
|
||||||
if time.Now().After(nextUpdate) {
|
|
||||||
logger.Debugf("CRL bundle retrieved from file cache has expired at %s", nextUpdate)
|
|
||||||
return corecrl.ErrCacheMiss
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
|
@ -1,395 +0,0 @@
|
||||||
// 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 crl
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"crypto/rand"
|
|
||||||
"crypto/x509"
|
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
|
||||||
"math/big"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"reflect"
|
|
||||||
"runtime"
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
corecrl "github.com/notaryproject/notation-core-go/revocation/crl"
|
|
||||||
"github.com/notaryproject/notation-core-go/testhelper"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestCache(t *testing.T) {
|
|
||||||
t.Run("file cache implement Cache interface", func(t *testing.T) {
|
|
||||||
root := t.TempDir()
|
|
||||||
var coreCache corecrl.Cache
|
|
||||||
var err error
|
|
||||||
coreCache, err = NewFileCache(root)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
if _, ok := coreCache.(*FileCache); !ok {
|
|
||||||
t.Fatal("FileCache does not implement coreCache")
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestFileCache(t *testing.T) {
|
|
||||||
now := time.Now()
|
|
||||||
certChain := testhelper.GetRevokableRSAChainWithRevocations(2, false, true)
|
|
||||||
crlBytes, err := x509.CreateRevocationList(rand.Reader, &x509.RevocationList{
|
|
||||||
Number: big.NewInt(1),
|
|
||||||
NextUpdate: now.Add(time.Hour),
|
|
||||||
}, certChain[1].Cert, certChain[1].PrivateKey)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
baseCRL, err := x509.ParseRevocationList(crlBytes)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx := context.Background()
|
|
||||||
root := t.TempDir()
|
|
||||||
cache, err := NewFileCache(root)
|
|
||||||
t.Run("NewFileCache", func(t *testing.T) {
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("expected no error, but got %v", err)
|
|
||||||
}
|
|
||||||
if cache.root != root {
|
|
||||||
t.Fatalf("expected root %v, but got %v", root, cache.root)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
key := "http://example.com"
|
|
||||||
t.Run("comformance", func(t *testing.T) {
|
|
||||||
bundle := &corecrl.Bundle{BaseCRL: baseCRL}
|
|
||||||
if err := cache.Set(ctx, key, bundle); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
retrievedBundle, err := cache.Get(ctx, key)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !reflect.DeepEqual(retrievedBundle.BaseCRL, bundle.BaseCRL) {
|
|
||||||
t.Fatalf("expected BaseCRL %+v, but got %+v", bundle.BaseCRL, retrievedBundle.BaseCRL)
|
|
||||||
}
|
|
||||||
|
|
||||||
if bundle.DeltaCRL != nil {
|
|
||||||
t.Fatalf("expected DeltaCRL to be nil, but got %+v", retrievedBundle.DeltaCRL)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("comformance with delta crl", func(t *testing.T) {
|
|
||||||
crlBytes, err := x509.CreateRevocationList(rand.Reader, &x509.RevocationList{
|
|
||||||
Number: big.NewInt(2),
|
|
||||||
NextUpdate: now.Add(time.Hour),
|
|
||||||
}, certChain[1].Cert, certChain[1].PrivateKey)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
deltaCRL, err := x509.ParseRevocationList(crlBytes)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
bundle := &corecrl.Bundle{BaseCRL: baseCRL, DeltaCRL: deltaCRL}
|
|
||||||
if err := cache.Set(ctx, key, bundle); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
retrievedBundle, err := cache.Get(ctx, key)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !reflect.DeepEqual(retrievedBundle.BaseCRL, bundle.BaseCRL) {
|
|
||||||
t.Fatalf("expected BaseCRL %+v, but got %+v", bundle.BaseCRL, retrievedBundle.BaseCRL)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !reflect.DeepEqual(retrievedBundle.DeltaCRL, bundle.DeltaCRL) {
|
|
||||||
t.Fatalf("expected DeltaCRL %+v, but got %+v", bundle.DeltaCRL, retrievedBundle.DeltaCRL)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestNewFileCacheFailed(t *testing.T) {
|
|
||||||
tempDir := t.TempDir()
|
|
||||||
t.Run("without permission to create cache directory", func(t *testing.T) {
|
|
||||||
if runtime.GOOS == "windows" {
|
|
||||||
t.Skip("skipping test on Windows")
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := os.Chmod(tempDir, 0); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
root := filepath.Join(tempDir, "test")
|
|
||||||
_, err := NewFileCache(root)
|
|
||||||
if !strings.Contains(err.Error(), "permission denied") {
|
|
||||||
t.Fatalf("expected permission denied error, but got %v", err)
|
|
||||||
}
|
|
||||||
// restore permission
|
|
||||||
if err := os.Chmod(tempDir, 0755); err != nil {
|
|
||||||
t.Fatalf("failed to change permission: %v", err)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGetFailed(t *testing.T) {
|
|
||||||
tempDir := t.TempDir()
|
|
||||||
cache, err := NewFileCache(tempDir)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
t.Run("key does not exist", func(t *testing.T) {
|
|
||||||
_, err := cache.Get(context.Background(), "nonExistKey")
|
|
||||||
if !errors.Is(err, corecrl.ErrCacheMiss) {
|
|
||||||
t.Fatalf("expected ErrCacheMiss, but got %v", err)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
invalidFile := filepath.Join(tempDir, cache.fileName("invalid"))
|
|
||||||
if err := os.WriteFile(invalidFile, []byte("invalid"), 0644); err != nil {
|
|
||||||
t.Fatalf("failed to write file: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
t.Run("no permission to read file", func(t *testing.T) {
|
|
||||||
if runtime.GOOS == "windows" {
|
|
||||||
t.Skip("skipping test on Windows")
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := os.Chmod(invalidFile, 0); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
_, err := cache.Get(context.Background(), "invalid")
|
|
||||||
if err == nil || !strings.Contains(err.Error(), "permission denied") {
|
|
||||||
t.Fatalf("expected permission denied error, but got %v", err)
|
|
||||||
}
|
|
||||||
// restore permission
|
|
||||||
if err := os.Chmod(invalidFile, 0755); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("invalid content", func(t *testing.T) {
|
|
||||||
_, err := cache.Get(context.Background(), "invalid")
|
|
||||||
expectedErrMsg := "failed to decode file retrieved from file cache: invalid character 'i' looking for beginning of value"
|
|
||||||
if err == nil || err.Error() != expectedErrMsg {
|
|
||||||
t.Fatalf("expected %s, but got %v", expectedErrMsg, err)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
now := time.Now()
|
|
||||||
certChain := testhelper.GetRevokableRSAChainWithRevocations(2, false, true)
|
|
||||||
crlBytes, err := x509.CreateRevocationList(rand.Reader, &x509.RevocationList{
|
|
||||||
Number: big.NewInt(1),
|
|
||||||
}, certChain[1].Cert, certChain[1].PrivateKey)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("failed to create base CRL: %v", err)
|
|
||||||
}
|
|
||||||
baseCRL, err := x509.ParseRevocationList(crlBytes)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("failed to parse base CRL: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
t.Run("empty RawBaseCRL of content", func(t *testing.T) {
|
|
||||||
content := fileCacheContent{
|
|
||||||
BaseCRL: []byte{},
|
|
||||||
}
|
|
||||||
b, err := json.Marshal(content)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
invalidBundleFile := filepath.Join(tempDir, cache.fileName("invalidBundle"))
|
|
||||||
if err := os.WriteFile(invalidBundleFile, b, 0644); err != nil {
|
|
||||||
t.Fatalf("failed to write file: %v", err)
|
|
||||||
}
|
|
||||||
_, err = cache.Get(context.Background(), "invalidBundle")
|
|
||||||
expectedErrMsg := "failed to parse base CRL of file retrieved from file cache: x509: malformed crl"
|
|
||||||
if err == nil || err.Error() != expectedErrMsg {
|
|
||||||
t.Fatalf("expected %s, but got %v", expectedErrMsg, err)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("invalid RawBaseCRL of content", func(t *testing.T) {
|
|
||||||
content := fileCacheContent{
|
|
||||||
BaseCRL: []byte("invalid"),
|
|
||||||
}
|
|
||||||
b, err := json.Marshal(content)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
invalidBundleFile := filepath.Join(tempDir, cache.fileName("invalidBundle"))
|
|
||||||
if err := os.WriteFile(invalidBundleFile, b, 0644); err != nil {
|
|
||||||
t.Fatalf("failed to write file: %v", err)
|
|
||||||
}
|
|
||||||
_, err = cache.Get(context.Background(), "invalidBundle")
|
|
||||||
expectedErrMsg := "failed to parse base CRL of file retrieved from file cache: x509: malformed crl"
|
|
||||||
if err == nil || err.Error() != expectedErrMsg {
|
|
||||||
t.Fatalf("expected %s, but got %v", expectedErrMsg, err)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("invalid RawDeltaCRL of content", func(t *testing.T) {
|
|
||||||
content := fileCacheContent{
|
|
||||||
BaseCRL: baseCRL.Raw,
|
|
||||||
DeltaCRL: []byte("invalid"),
|
|
||||||
}
|
|
||||||
b, err := json.Marshal(content)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
invalidBundleFile := filepath.Join(tempDir, cache.fileName("invalidBundle"))
|
|
||||||
if err := os.WriteFile(invalidBundleFile, b, 0644); err != nil {
|
|
||||||
t.Fatalf("failed to write file: %v", err)
|
|
||||||
}
|
|
||||||
_, err = cache.Get(context.Background(), "invalidBundle")
|
|
||||||
expectedErrMsg := "failed to parse delta CRL of file retrieved from file cache: x509: malformed crl"
|
|
||||||
if err == nil || err.Error() != expectedErrMsg {
|
|
||||||
t.Fatalf("expected %s, but got %v", expectedErrMsg, err)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("bundle with invalid NextUpdate", func(t *testing.T) {
|
|
||||||
ctx := context.Background()
|
|
||||||
expiredBundle := &corecrl.Bundle{BaseCRL: baseCRL}
|
|
||||||
if err := cache.Set(ctx, "expiredKey", expiredBundle); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
_, err = cache.Get(ctx, "expiredKey")
|
|
||||||
expectedErrMsg := "check BaseCRL expiry failed: crl bundle retrieved from file cache does not contain valid NextUpdate"
|
|
||||||
if err == nil || err.Error() != expectedErrMsg {
|
|
||||||
t.Fatalf("expected %s, but got %v", expectedErrMsg, err)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
crlBytes, err = x509.CreateRevocationList(rand.Reader, &x509.RevocationList{
|
|
||||||
Number: big.NewInt(1),
|
|
||||||
NextUpdate: now.Add(-time.Hour),
|
|
||||||
}, certChain[1].Cert, certChain[1].PrivateKey)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("failed to create base CRL: %v", err)
|
|
||||||
}
|
|
||||||
expiredBaseCRL, err := x509.ParseRevocationList(crlBytes)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("failed to parse base CRL: %v", err)
|
|
||||||
}
|
|
||||||
t.Run("base crl in cache has expired", func(t *testing.T) {
|
|
||||||
ctx := context.Background()
|
|
||||||
expiredBundle := &corecrl.Bundle{BaseCRL: expiredBaseCRL}
|
|
||||||
if err := cache.Set(ctx, "expiredKey", expiredBundle); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
_, err = cache.Get(ctx, "expiredKey")
|
|
||||||
if !errors.Is(err, corecrl.ErrCacheMiss) {
|
|
||||||
t.Fatalf("expected ErrCacheMiss, but got %v", err)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("delta crl in cache has expired", func(t *testing.T) {
|
|
||||||
ctx := context.Background()
|
|
||||||
crlBytes, err := x509.CreateRevocationList(rand.Reader, &x509.RevocationList{
|
|
||||||
Number: big.NewInt(1),
|
|
||||||
NextUpdate: now.Add(time.Hour),
|
|
||||||
}, certChain[1].Cert, certChain[1].PrivateKey)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("failed to create base CRL: %v", err)
|
|
||||||
}
|
|
||||||
baseCRL, err := x509.ParseRevocationList(crlBytes)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("failed to parse base CRL: %v", err)
|
|
||||||
}
|
|
||||||
crlBytes, err = x509.CreateRevocationList(rand.Reader, &x509.RevocationList{
|
|
||||||
Number: big.NewInt(1),
|
|
||||||
NextUpdate: now.Add(-time.Hour),
|
|
||||||
}, certChain[1].Cert, certChain[1].PrivateKey)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("failed to create base CRL: %v", err)
|
|
||||||
}
|
|
||||||
expiredDeltaCRL, err := x509.ParseRevocationList(crlBytes)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("failed to parse base CRL: %v", err)
|
|
||||||
}
|
|
||||||
expiredBundle := &corecrl.Bundle{BaseCRL: baseCRL, DeltaCRL: expiredDeltaCRL}
|
|
||||||
if err := cache.Set(ctx, "expiredKey", expiredBundle); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
_, err = cache.Get(ctx, "expiredKey")
|
|
||||||
if !errors.Is(err, corecrl.ErrCacheMiss) {
|
|
||||||
t.Fatalf("expected ErrCacheMiss, but got %v", err)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSetFailed(t *testing.T) {
|
|
||||||
tempDir := t.TempDir()
|
|
||||||
cache, err := NewFileCache(tempDir)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
now := time.Now()
|
|
||||||
certChain := testhelper.GetRevokableRSAChainWithRevocations(2, false, true)
|
|
||||||
crlBytes, err := x509.CreateRevocationList(rand.Reader, &x509.RevocationList{
|
|
||||||
Number: big.NewInt(1),
|
|
||||||
NextUpdate: now.Add(time.Hour),
|
|
||||||
}, certChain[1].Cert, certChain[1].PrivateKey)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
baseCRL, err := x509.ParseRevocationList(crlBytes)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
ctx := context.Background()
|
|
||||||
key := "testKey"
|
|
||||||
|
|
||||||
t.Run("nil bundle", func(t *testing.T) {
|
|
||||||
err := cache.Set(ctx, key, nil)
|
|
||||||
expectedErrMsg := "failed to store crl bundle in file cache: bundle cannot be nil"
|
|
||||||
if err == nil || err.Error() != expectedErrMsg {
|
|
||||||
t.Fatalf("expected %s, but got %v", expectedErrMsg, err)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("nil bundle BaseCRL", func(t *testing.T) {
|
|
||||||
bundle := &corecrl.Bundle{}
|
|
||||||
err := cache.Set(ctx, key, bundle)
|
|
||||||
expectedErrMsg := "failed to store crl bundle in file cache: bundle BaseCRL cannot be nil"
|
|
||||||
if err == nil || err.Error() != expectedErrMsg {
|
|
||||||
t.Fatalf("expected %s, but got %v", expectedErrMsg, err)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("failed to write into cache due to permission denied", func(t *testing.T) {
|
|
||||||
if runtime.GOOS == "windows" {
|
|
||||||
t.Skip("skipping test on Windows")
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := os.Chmod(tempDir, 0); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
bundle := &corecrl.Bundle{BaseCRL: baseCRL}
|
|
||||||
err := cache.Set(ctx, key, bundle)
|
|
||||||
if err == nil || !strings.Contains(err.Error(), "permission denied") {
|
|
||||||
t.Fatalf("expected permission denied error, but got %v", err)
|
|
||||||
}
|
|
||||||
// restore permission
|
|
||||||
if err := os.Chmod(tempDir, 0755); err != nil {
|
|
||||||
t.Fatalf("failed to change permission: %v", err)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
|
@ -60,10 +60,10 @@ func loadX509TrustStores(ctx context.Context, scheme signature.SigningScheme, po
|
||||||
return loadX509TrustStoresWithType(ctx, typeToLoad, policyName, trustStores, x509TrustStore)
|
return loadX509TrustStoresWithType(ctx, typeToLoad, policyName, trustStores, x509TrustStore)
|
||||||
}
|
}
|
||||||
|
|
||||||
// isCriticalFailure checks whether a [notation.ValidationResult] fails the
|
// isCriticalFailure checks whether a VerificationResult fails the entire
|
||||||
// entire signature verification workflow.
|
// signature verification workflow.
|
||||||
// signature verification workflow is considered failed if there is a
|
// signature verification workflow is considered failed if there is a
|
||||||
// ValidationResult with "Enforced" as the action but the result was
|
// VerificationResult with "Enforced" as the action but the result was
|
||||||
// unsuccessful.
|
// unsuccessful.
|
||||||
func isCriticalFailure(result *notation.ValidationResult) bool {
|
func isCriticalFailure(result *notation.ValidationResult) bool {
|
||||||
return result.Action == trustpolicy.ActionEnforce && result.Error != nil
|
return result.Action == trustpolicy.ActionEnforce && result.Error != nil
|
||||||
|
|
|
@ -60,7 +60,7 @@ func TestLoadX509TrustStore(t *testing.T) {
|
||||||
// load "ca" and "signingAuthority" trust store
|
// load "ca" and "signingAuthority" trust store
|
||||||
caStore := "ca:valid-trust-store"
|
caStore := "ca:valid-trust-store"
|
||||||
signingAuthorityStore := "signingAuthority:valid-trust-store"
|
signingAuthorityStore := "signingAuthority:valid-trust-store"
|
||||||
dummyPolicy := dummyOCIPolicyDocument().TrustPolicies[0]
|
dummyPolicy := dummyPolicyDocument().TrustPolicies[0]
|
||||||
dummyPolicy.TrustStores = []string{caStore, signingAuthorityStore}
|
dummyPolicy.TrustStores = []string{caStore, signingAuthorityStore}
|
||||||
dir.UserConfigDir = "testdata"
|
dir.UserConfigDir = "testdata"
|
||||||
x509truststore := truststore.NewX509TrustStore(dir.ConfigFS())
|
x509truststore := truststore.NewX509TrustStore(dir.ConfigFS())
|
||||||
|
@ -138,10 +138,10 @@ func getArtifactDigestFromReference(artifactReference string) (string, error) {
|
||||||
return artifactReference[i+1:], nil
|
return artifactReference[i+1:], nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func dummyOCIPolicyDocument() (policyDoc trustpolicy.OCIDocument) {
|
func dummyPolicyDocument() (policyDoc trustpolicy.Document) {
|
||||||
return trustpolicy.OCIDocument{
|
return trustpolicy.Document{
|
||||||
Version: "1.0",
|
Version: "1.0",
|
||||||
TrustPolicies: []trustpolicy.OCITrustPolicy{
|
TrustPolicies: []trustpolicy.TrustPolicy{
|
||||||
{
|
{
|
||||||
Name: "test-statement-name",
|
Name: "test-statement-name",
|
||||||
RegistryScopes: []string{"registry.acme-rockets.io/software/net-monitor"},
|
RegistryScopes: []string{"registry.acme-rockets.io/software/net-monitor"},
|
||||||
|
@ -153,15 +153,11 @@ func dummyOCIPolicyDocument() (policyDoc trustpolicy.OCIDocument) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func dummyBlobPolicyDocument() (policyDoc trustpolicy.BlobDocument) {
|
func dummyInvalidPolicyDocument() (policyDoc trustpolicy.Document) {
|
||||||
return trustpolicy.BlobDocument{
|
return trustpolicy.Document{
|
||||||
Version: "1.0",
|
TrustPolicies: []trustpolicy.TrustPolicy{
|
||||||
TrustPolicies: []trustpolicy.BlobTrustPolicy{
|
|
||||||
{
|
{
|
||||||
Name: "blob-test-statement-name",
|
Name: "invalid",
|
||||||
SignatureVerification: trustpolicy.SignatureVerification{VerificationLevel: "strict"},
|
|
||||||
TrustStores: []string{"ca:valid-trust-store", "signingAuthority:valid-trust-store"},
|
|
||||||
TrustedIdentities: []string{"x509.subject:CN=Notation Test Root,O=Notary,L=Seattle,ST=WA,C=US"},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
Binary file not shown.
Binary file not shown.
|
@ -216,7 +216,7 @@ func TestAuthenticTimestamp(t *testing.T) {
|
||||||
VerificationLevel: trustpolicy.LevelStrict,
|
VerificationLevel: trustpolicy.LevelStrict,
|
||||||
}
|
}
|
||||||
authenticTimestampResult := verifyAuthenticTimestamp(context.Background(), dummyTrustPolicy.Name, dummyTrustPolicy.TrustStores, dummyTrustPolicy.SignatureVerification, trustStore, revocationTimestampingValidator, outcome)
|
authenticTimestampResult := verifyAuthenticTimestamp(context.Background(), dummyTrustPolicy.Name, dummyTrustPolicy.TrustStores, dummyTrustPolicy.SignatureVerification, trustStore, revocationTimestampingValidator, outcome)
|
||||||
expectedErrMsg := "failed to parse timestamp countersignature with error: unexpected content type: 1.2.840.113549.1.7.1. Expected to be id-ct-TSTInfo (1.2.840.113549.1.9.16.1.4)"
|
expectedErrMsg := "failed to parse timestamp countersignature with error: unexpected content type: 1.2.840.113549.1.7.1"
|
||||||
if err := authenticTimestampResult.Error; err == nil || err.Error() != expectedErrMsg {
|
if err := authenticTimestampResult.Error; err == nil || err.Error() != expectedErrMsg {
|
||||||
t.Fatalf("expected %s, but got %s", expectedErrMsg, err)
|
t.Fatalf("expected %s, but got %s", expectedErrMsg, err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,148 +0,0 @@
|
||||||
// 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 (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"reflect"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/notaryproject/notation-go/dir"
|
|
||||||
set "github.com/notaryproject/notation-go/internal/container"
|
|
||||||
"github.com/notaryproject/notation-go/internal/slices"
|
|
||||||
)
|
|
||||||
|
|
||||||
// BlobDocument represents a trustpolicy.blob.json document for arbitrary blobs
|
|
||||||
type BlobDocument struct {
|
|
||||||
// Version of the policy document
|
|
||||||
Version string `json:"version"`
|
|
||||||
|
|
||||||
// TrustPolicies include each policy statement
|
|
||||||
TrustPolicies []BlobTrustPolicy `json:"trustPolicies"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// BlobTrustPolicy represents a policy statement in the blob trust policy
|
|
||||||
// document
|
|
||||||
type BlobTrustPolicy struct {
|
|
||||||
// Name of the policy statement
|
|
||||||
Name string `json:"name"`
|
|
||||||
|
|
||||||
// SignatureVerification setting for this policy statement
|
|
||||||
SignatureVerification SignatureVerification `json:"signatureVerification"`
|
|
||||||
|
|
||||||
// TrustStores this policy statement uses
|
|
||||||
TrustStores []string `json:"trustStores"`
|
|
||||||
|
|
||||||
// TrustedIdentities this policy statement pins
|
|
||||||
TrustedIdentities []string `json:"trustedIdentities"`
|
|
||||||
|
|
||||||
// GlobalPolicy defines if policy statement is global or not
|
|
||||||
GlobalPolicy bool `json:"globalPolicy,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
var supportedBlobPolicyVersions = []string{"1.0"}
|
|
||||||
|
|
||||||
// LoadBlobDocument loads a blob trust policy document from a local file system
|
|
||||||
func LoadBlobDocument() (*BlobDocument, error) {
|
|
||||||
var doc BlobDocument
|
|
||||||
err := getDocument(dir.PathBlobTrustPolicy, &doc)
|
|
||||||
return &doc, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate validates a blob trust policy document according to its version's
|
|
||||||
// rule set.
|
|
||||||
// If any rule is violated, returns an error.
|
|
||||||
func (policyDoc *BlobDocument) Validate() error {
|
|
||||||
// sanity check
|
|
||||||
if policyDoc == nil {
|
|
||||||
return errors.New("blob trust policy document cannot be nil")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate Version
|
|
||||||
if policyDoc.Version == "" {
|
|
||||||
return errors.New("blob trust policy document has empty version, version must be specified")
|
|
||||||
}
|
|
||||||
if !slices.Contains(supportedBlobPolicyVersions, policyDoc.Version) {
|
|
||||||
return fmt.Errorf("blob trust policy document uses unsupported version %q", policyDoc.Version)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate the policy according to 1.0 rules
|
|
||||||
if len(policyDoc.TrustPolicies) == 0 {
|
|
||||||
return errors.New("blob trust policy document can not have zero trust policy statements")
|
|
||||||
}
|
|
||||||
policyNames := set.New[string]()
|
|
||||||
var foundGlobalPolicy bool
|
|
||||||
for _, statement := range policyDoc.TrustPolicies {
|
|
||||||
// Verify unique policy statement names across the policy document
|
|
||||||
if policyNames.Contains(statement.Name) {
|
|
||||||
return fmt.Errorf("multiple blob trust policy statements use the same name %q, statement names must be unique", statement.Name)
|
|
||||||
}
|
|
||||||
if err := validatePolicyCore(statement.Name, statement.SignatureVerification, statement.TrustStores, statement.TrustedIdentities); err != nil {
|
|
||||||
return fmt.Errorf("blob trust policy: %w", err)
|
|
||||||
}
|
|
||||||
if statement.GlobalPolicy {
|
|
||||||
if foundGlobalPolicy {
|
|
||||||
return errors.New("multiple blob trust policy statements have globalPolicy set to true. Only one trust policy statement can be marked as global policy")
|
|
||||||
}
|
|
||||||
|
|
||||||
// verificationLevel is skip
|
|
||||||
if reflect.DeepEqual(statement.SignatureVerification.VerificationLevel, LevelSkip) {
|
|
||||||
return errors.New("global blob trust policy statement cannot have verification level set to skip")
|
|
||||||
}
|
|
||||||
foundGlobalPolicy = true
|
|
||||||
}
|
|
||||||
policyNames.Add(statement.Name)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetApplicableTrustPolicy returns a pointer to the deep copied [BlobTrustPolicy]
|
|
||||||
// for given policy name.
|
|
||||||
// see https://github.com/notaryproject/specifications/tree/9c81dc773508dedc5a81c02c8d805de04f65050b/specs/trust-store-trust-policy.md#blob-trust-policy
|
|
||||||
func (policyDoc *BlobDocument) GetApplicableTrustPolicy(policyName string) (*BlobTrustPolicy, error) {
|
|
||||||
if strings.TrimSpace(policyName) == "" {
|
|
||||||
return nil, errors.New("policy name cannot be empty")
|
|
||||||
}
|
|
||||||
for _, policyStatement := range policyDoc.TrustPolicies {
|
|
||||||
// exact match
|
|
||||||
if policyStatement.Name == policyName {
|
|
||||||
return (&policyStatement).clone(), nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil, fmt.Errorf("no applicable blob trust policy with name %q", policyName)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetGlobalTrustPolicy returns a pointer to the deep copied [BlobTrustPolicy]
|
|
||||||
// that is marked as global policy.
|
|
||||||
// see https://github.com/notaryproject/specifications/tree/9c81dc773508dedc5a81c02c8d805de04f65050b/specs/trust-store-trust-policy.md#blob-trust-policy
|
|
||||||
func (policyDoc *BlobDocument) GetGlobalTrustPolicy() (*BlobTrustPolicy, error) {
|
|
||||||
for _, policyStatement := range policyDoc.TrustPolicies {
|
|
||||||
if policyStatement.GlobalPolicy {
|
|
||||||
return (&policyStatement).clone(), nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil, fmt.Errorf("no global blob trust policy")
|
|
||||||
}
|
|
||||||
|
|
||||||
// clone returns a pointer to the deep copied [BlobTrustPolicy]
|
|
||||||
func (t *BlobTrustPolicy) clone() *BlobTrustPolicy {
|
|
||||||
return &BlobTrustPolicy{
|
|
||||||
Name: t.Name,
|
|
||||||
SignatureVerification: t.SignatureVerification,
|
|
||||||
TrustedIdentities: append([]string(nil), t.TrustedIdentities...),
|
|
||||||
TrustStores: append([]string(nil), t.TrustStores...),
|
|
||||||
GlobalPolicy: t.GlobalPolicy,
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,168 +0,0 @@
|
||||||
// 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 (
|
|
||||||
"encoding/json"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"reflect"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/notaryproject/notation-go/dir"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestLoadBlobDocument(t *testing.T) {
|
|
||||||
tempRoot := t.TempDir()
|
|
||||||
dir.UserConfigDir = tempRoot
|
|
||||||
path := filepath.Join(tempRoot, "trustpolicy.blob.json")
|
|
||||||
policyJson, _ := json.Marshal(dummyBlobPolicyDocument())
|
|
||||||
if err := os.WriteFile(path, policyJson, 0600); err != nil {
|
|
||||||
t.Fatalf("TestLoadBlobDocument write policy file failed. Error: %v", err)
|
|
||||||
}
|
|
||||||
t.Cleanup(func() { os.RemoveAll(tempRoot) })
|
|
||||||
|
|
||||||
if _, err := LoadBlobDocument(); err != nil {
|
|
||||||
t.Fatalf("LoadBlobDocument() should not throw error for an existing policy file. Error: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestValidate_BlobDocument(t *testing.T) {
|
|
||||||
policyDoc := dummyBlobPolicyDocument()
|
|
||||||
if err := policyDoc.Validate(); err != nil {
|
|
||||||
t.Fatalf("Validate() returned error: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestValidate_BlobDocument_Error(t *testing.T) {
|
|
||||||
// Sanity check
|
|
||||||
var nilPolicyDoc *BlobDocument
|
|
||||||
err := nilPolicyDoc.Validate()
|
|
||||||
if err == nil || err.Error() != "blob trust policy document cannot be nil" {
|
|
||||||
t.Fatalf("nil policyDoc should return error")
|
|
||||||
}
|
|
||||||
|
|
||||||
// empty Version
|
|
||||||
policyDoc := dummyBlobPolicyDocument()
|
|
||||||
policyDoc.Version = ""
|
|
||||||
err = policyDoc.Validate()
|
|
||||||
if err == nil || err.Error() != "blob trust policy document has empty version, version must be specified" {
|
|
||||||
t.Fatalf("empty version should return error")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Invalid Version
|
|
||||||
policyDoc = dummyBlobPolicyDocument()
|
|
||||||
policyDoc.Version = "invalid"
|
|
||||||
err = policyDoc.Validate()
|
|
||||||
if err == nil || err.Error() != "blob trust policy document uses unsupported version \"invalid\"" {
|
|
||||||
t.Fatalf("invalid version should return error")
|
|
||||||
}
|
|
||||||
|
|
||||||
// No Policy Statements
|
|
||||||
policyDoc = dummyBlobPolicyDocument()
|
|
||||||
policyDoc.TrustPolicies = nil
|
|
||||||
err = policyDoc.Validate()
|
|
||||||
if err == nil || err.Error() != "blob trust policy document can not have zero trust policy statements" {
|
|
||||||
t.Fatalf("zero policy statements should return error")
|
|
||||||
}
|
|
||||||
|
|
||||||
// No Policy Statement Name
|
|
||||||
policyDoc = dummyBlobPolicyDocument()
|
|
||||||
policyDoc.TrustPolicies[0].Name = ""
|
|
||||||
err = policyDoc.Validate()
|
|
||||||
if err == nil || err.Error() != "blob trust policy: a trust policy statement is missing a name, every statement requires a name" {
|
|
||||||
t.Fatalf("policy statement with no name should return an error")
|
|
||||||
}
|
|
||||||
|
|
||||||
// multiple global rust policy
|
|
||||||
policyDoc = dummyBlobPolicyDocument()
|
|
||||||
policyStatement1 := policyDoc.TrustPolicies[0].clone()
|
|
||||||
policyStatement1.GlobalPolicy = true
|
|
||||||
policyStatement2 := policyDoc.TrustPolicies[0].clone()
|
|
||||||
policyStatement2.Name = "test-statement-name-2"
|
|
||||||
policyStatement2.GlobalPolicy = true
|
|
||||||
policyDoc.TrustPolicies = []BlobTrustPolicy{*policyStatement1, *policyStatement2}
|
|
||||||
err = policyDoc.Validate()
|
|
||||||
if err == nil || err.Error() != "multiple blob trust policy statements have globalPolicy set to true. Only one trust policy statement can be marked as global policy" {
|
|
||||||
t.Error(err)
|
|
||||||
t.Fatalf("multiple global blob policy should return error")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Policy Document with duplicate policy statement names
|
|
||||||
policyDoc = dummyBlobPolicyDocument()
|
|
||||||
policyStatement1 = policyDoc.TrustPolicies[0].clone()
|
|
||||||
policyStatement2 = policyDoc.TrustPolicies[0].clone()
|
|
||||||
policyDoc.TrustPolicies = []BlobTrustPolicy{*policyStatement1, *policyStatement2}
|
|
||||||
err = policyDoc.Validate()
|
|
||||||
if err == nil || err.Error() != "multiple blob trust policy statements use the same name \"test-statement-name\", statement names must be unique" {
|
|
||||||
t.Fatalf("policy statements with same name should return error")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGetApplicableTrustPolicy(t *testing.T) {
|
|
||||||
policyDoc := dummyBlobPolicyDocument()
|
|
||||||
|
|
||||||
policyStatement := policyDoc.TrustPolicies[0].clone()
|
|
||||||
policyStatement1 := policyStatement.clone()
|
|
||||||
policyStatement1.Name = "test-statement-name-1"
|
|
||||||
policyStatement1.GlobalPolicy = true
|
|
||||||
policyStatement2 := policyStatement.clone()
|
|
||||||
policyStatement2.Name = "test-statement-name-2"
|
|
||||||
policyDoc.TrustPolicies = []BlobTrustPolicy{*policyStatement, *policyStatement1, *policyStatement2}
|
|
||||||
|
|
||||||
validateGetApplicableTrustPolicy(t, policyDoc, "test-statement-name-2", policyStatement2)
|
|
||||||
validateGetApplicableTrustPolicy(t, policyDoc, "test-statement-name", policyStatement)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGetApplicableTrustPolicy_Error(t *testing.T) {
|
|
||||||
policyDoc := dummyBlobPolicyDocument()
|
|
||||||
t.Run("empty policy name", func(t *testing.T) {
|
|
||||||
_, err := policyDoc.GetApplicableTrustPolicy("")
|
|
||||||
if err == nil || err.Error() != "policy name cannot be empty" {
|
|
||||||
t.Fatalf("GetApplicableTrustPolicy() returned error: %v", err)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("non existent policy name", func(t *testing.T) {
|
|
||||||
_, err := policyDoc.GetApplicableTrustPolicy("blaah")
|
|
||||||
if err == nil || err.Error() != "no applicable blob trust policy with name \"blaah\"" {
|
|
||||||
t.Fatalf("GetApplicableTrustPolicy() returned error: %v", err)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGetGlobalTrustPolicy(t *testing.T) {
|
|
||||||
policyDoc := dummyBlobPolicyDocument()
|
|
||||||
policyDoc.TrustPolicies[0].GlobalPolicy = true
|
|
||||||
|
|
||||||
policy, err := policyDoc.GetGlobalTrustPolicy()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("GetGlobalTrustPolicy() returned error: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !reflect.DeepEqual(*policy, policyDoc.TrustPolicies[0]) {
|
|
||||||
t.Fatalf("GetGlobalTrustPolicy() returned unexpected policy")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func validateGetApplicableTrustPolicy(t *testing.T, policyDoc BlobDocument, policyName string, expectedPolicy *BlobTrustPolicy) {
|
|
||||||
policy, err := policyDoc.GetApplicableTrustPolicy(policyName)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("GetApplicableTrustPolicy() returned error: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if reflect.DeepEqual(policy, *expectedPolicy) {
|
|
||||||
t.Fatalf("GetApplicableTrustPolicy() returned unexpected policy for %s", policyName)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -25,20 +25,23 @@ import (
|
||||||
"github.com/notaryproject/notation-go/internal/trustpolicy"
|
"github.com/notaryproject/notation-go/internal/trustpolicy"
|
||||||
)
|
)
|
||||||
|
|
||||||
// OCIDocument represents a trustpolicy.oci.json document for OCI artifacts
|
// Document represents a trustpolicy.json document
|
||||||
type OCIDocument struct {
|
type Document struct {
|
||||||
// Version of the policy document
|
// Version of the policy document
|
||||||
Version string `json:"version"`
|
Version string `json:"version"`
|
||||||
|
|
||||||
// TrustPolicies include each policy statement
|
// TrustPolicies include each policy statement
|
||||||
TrustPolicies []OCITrustPolicy `json:"trustPolicies"`
|
TrustPolicies []TrustPolicy `json:"trustPolicies"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// OCITrustPolicy represents a policy statement in the OCI trust policy document
|
// TrustPolicy represents a policy statement in the policy document
|
||||||
type OCITrustPolicy struct {
|
type TrustPolicy struct {
|
||||||
// Name of the policy statement
|
// Name of the policy statement
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
|
|
||||||
|
// RegistryScopes that this policy statement affects
|
||||||
|
RegistryScopes []string `json:"registryScopes"`
|
||||||
|
|
||||||
// SignatureVerification setting for this policy statement
|
// SignatureVerification setting for this policy statement
|
||||||
SignatureVerification SignatureVerification `json:"signatureVerification"`
|
SignatureVerification SignatureVerification `json:"signatureVerification"`
|
||||||
|
|
||||||
|
@ -47,50 +50,14 @@ type OCITrustPolicy struct {
|
||||||
|
|
||||||
// TrustedIdentities this policy statement pins
|
// TrustedIdentities this policy statement pins
|
||||||
TrustedIdentities []string `json:"trustedIdentities"`
|
TrustedIdentities []string `json:"trustedIdentities"`
|
||||||
|
|
||||||
// RegistryScopes that this policy statement affects
|
|
||||||
RegistryScopes []string `json:"registryScopes"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Document represents a trustPolicy.json document
|
var supportedPolicyVersions = []string{"1.0"}
|
||||||
//
|
|
||||||
// Deprecated: Document exists for historical compatibility and
|
|
||||||
// should not be used. To create OCI Document, use [OCIDocument].
|
|
||||||
type Document = OCIDocument
|
|
||||||
|
|
||||||
// TrustPolicy represents a policy statement in the policy document
|
// LoadDocument retrieves a trust policy document from the local file system.
|
||||||
//
|
func LoadDocument() (*Document, error) {
|
||||||
// Deprecated: TrustPolicy exists for historical compatibility and
|
var doc Document
|
||||||
// should not be used. To create OCI TrustPolicy, use [OCITrustPolicy].
|
if err := getDocument(dir.PathTrustPolicy, &doc); err != nil {
|
||||||
type TrustPolicy = OCITrustPolicy
|
|
||||||
|
|
||||||
// LoadDocument loads a trust policy document from a local file system
|
|
||||||
//
|
|
||||||
// Deprecated: LoadDocument function exists for historical compatibility and
|
|
||||||
// should not be used. To load OCI Document, use [LoadOCIDocument] function.
|
|
||||||
var LoadDocument = LoadOCIDocument
|
|
||||||
|
|
||||||
var supportedOCIPolicyVersions = []string{"1.0"}
|
|
||||||
|
|
||||||
// LoadOCIDocument retrieves a trust policy document from the local file system.
|
|
||||||
// It attempts to read from [dir.PathOCITrustPolicy] first; if not found,
|
|
||||||
// it tries [dir.PathTrustPolicy].
|
|
||||||
// If both dir.PathOCITrustPolicy and dir.PathTrustPolicy exist,
|
|
||||||
// dir.PathOCITrustPolicy will be read.
|
|
||||||
func LoadOCIDocument() (*OCIDocument, error) {
|
|
||||||
var doc OCIDocument
|
|
||||||
|
|
||||||
// attempt to load the document from dir.PathOCITrustPolicy
|
|
||||||
if err := getDocument(dir.PathOCITrustPolicy, &doc); err != nil {
|
|
||||||
// if the document is not found at the first path, try the second path
|
|
||||||
if errors.As(err, &errPolicyNotExist{}) {
|
|
||||||
if err := getDocument(dir.PathTrustPolicy, &doc); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &doc, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// if an error occurred other than the document not found, return it
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return &doc, nil
|
return &doc, nil
|
||||||
|
@ -98,33 +65,36 @@ func LoadOCIDocument() (*OCIDocument, error) {
|
||||||
|
|
||||||
// Validate validates a policy document according to its version's rule set.
|
// Validate validates a policy document according to its version's rule set.
|
||||||
// if any rule is violated, returns an error
|
// if any rule is violated, returns an error
|
||||||
func (policyDoc *OCIDocument) Validate() error {
|
func (policyDoc *Document) Validate() error {
|
||||||
// sanity check
|
// sanity check
|
||||||
if policyDoc == nil {
|
if policyDoc == nil {
|
||||||
return errors.New("oci trust policy document cannot be nil")
|
return errors.New("trust policy document cannot be nil")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate Version
|
// Validate Version
|
||||||
if policyDoc.Version == "" {
|
if policyDoc.Version == "" {
|
||||||
return errors.New("oci trust policy document has empty version, version must be specified")
|
return errors.New("trust policy document has empty version, version must be specified")
|
||||||
}
|
}
|
||||||
if !slices.Contains(supportedOCIPolicyVersions, policyDoc.Version) {
|
if !slices.Contains(supportedPolicyVersions, policyDoc.Version) {
|
||||||
return fmt.Errorf("oci trust policy document uses unsupported version %q", policyDoc.Version)
|
return fmt.Errorf("trust policy document uses unsupported version %q", policyDoc.Version)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate the policy according to 1.0 rules
|
// Validate the policy according to 1.0 rules
|
||||||
if len(policyDoc.TrustPolicies) == 0 {
|
if len(policyDoc.TrustPolicies) == 0 {
|
||||||
return errors.New("oci trust policy document can not have zero trust policy statements")
|
return errors.New("trust policy document can not have zero trust policy statements")
|
||||||
}
|
}
|
||||||
|
|
||||||
policyNames := set.New[string]()
|
policyNames := set.New[string]()
|
||||||
for _, statement := range policyDoc.TrustPolicies {
|
for _, statement := range policyDoc.TrustPolicies {
|
||||||
// Verify unique policy statement names across the policy document
|
// Verify unique policy statement names across the policy document
|
||||||
if policyNames.Contains(statement.Name) {
|
if policyNames.Contains(statement.Name) {
|
||||||
return fmt.Errorf("multiple oci trust policy statements use the same name %q, statement names must be unique", statement.Name)
|
return fmt.Errorf("multiple trust policy statements use the same name %q, statement names must be unique", statement.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := validatePolicyCore(statement.Name, statement.SignatureVerification, statement.TrustStores, statement.TrustedIdentities); err != nil {
|
if err := validatePolicyCore(statement.Name, statement.SignatureVerification, statement.TrustStores, statement.TrustedIdentities); err != nil {
|
||||||
return fmt.Errorf("oci trust policy: %w", err)
|
return fmt.Errorf("trust policy: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
policyNames.Add(statement.Name)
|
policyNames.Add(statement.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -132,21 +102,22 @@ func (policyDoc *OCIDocument) Validate() error {
|
||||||
if err := validateRegistryScopes(policyDoc); err != nil {
|
if err := validateRegistryScopes(policyDoc); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetApplicableTrustPolicy returns a pointer to the deep copied [OCITrustPolicy]
|
// GetApplicableTrustPolicy returns a pointer to the deep copied TrustPolicy
|
||||||
// statement that applies to the given registry scope. If no applicable trust
|
// statement that applies to the given registry scope. If no applicable trust
|
||||||
// policy is found, returns an error.
|
// policy is found, returns an error
|
||||||
// see https://github.com/notaryproject/specifications/tree/9c81dc773508dedc5a81c02c8d805de04f65050b/specs/trust-store-trust-policy.md#selecting-a-trust-policy-based-on-artifact-uri
|
// see https://github.com/notaryproject/notaryproject/blob/v1.0.0/specs/trust-store-trust-policy.md#selecting-a-trust-policy-based-on-artifact-uri
|
||||||
func (policyDoc *OCIDocument) GetApplicableTrustPolicy(artifactReference string) (*OCITrustPolicy, error) {
|
func (policyDoc *Document) GetApplicableTrustPolicy(artifactReference string) (*TrustPolicy, error) {
|
||||||
artifactPath, err := getArtifactPathFromReference(artifactReference)
|
artifactPath, err := getArtifactPathFromReference(artifactReference)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
var wildcardPolicy *OCITrustPolicy
|
var wildcardPolicy *TrustPolicy
|
||||||
var applicablePolicy *OCITrustPolicy
|
var applicablePolicy *TrustPolicy
|
||||||
for _, policyStatement := range policyDoc.TrustPolicies {
|
for _, policyStatement := range policyDoc.TrustPolicies {
|
||||||
if slices.Contains(policyStatement.RegistryScopes, trustpolicy.Wildcard) {
|
if slices.Contains(policyStatement.RegistryScopes, trustpolicy.Wildcard) {
|
||||||
// we need to deep copy because we can't use the loop variable
|
// we need to deep copy because we can't use the loop variable
|
||||||
|
@ -156,6 +127,7 @@ func (policyDoc *OCIDocument) GetApplicableTrustPolicy(artifactReference string)
|
||||||
applicablePolicy = (&policyStatement).clone()
|
applicablePolicy = (&policyStatement).clone()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if applicablePolicy != nil {
|
if applicablePolicy != nil {
|
||||||
// a policy with exact match for registry scope takes precedence over
|
// a policy with exact match for registry scope takes precedence over
|
||||||
// a wildcard (*) policy.
|
// a wildcard (*) policy.
|
||||||
|
@ -163,13 +135,13 @@ func (policyDoc *OCIDocument) GetApplicableTrustPolicy(artifactReference string)
|
||||||
} else if wildcardPolicy != nil {
|
} else if wildcardPolicy != nil {
|
||||||
return wildcardPolicy, nil
|
return wildcardPolicy, nil
|
||||||
} else {
|
} else {
|
||||||
return nil, fmt.Errorf("artifact %q has no applicable oci trust policy statement. Trust policy applicability for a given artifact is determined by registryScopes. To create a trust policy, see: %s", artifactReference, trustPolicyLink)
|
return nil, fmt.Errorf("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", artifactReference, trustPolicyLink)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// clone returns a pointer to the deep copied [OCITrustPolicy]
|
// clone returns a pointer to the deeply copied TrustPolicy
|
||||||
func (t *OCITrustPolicy) clone() *OCITrustPolicy {
|
func (t *TrustPolicy) clone() *TrustPolicy {
|
||||||
return &OCITrustPolicy{
|
return &TrustPolicy{
|
||||||
Name: t.Name,
|
Name: t.Name,
|
||||||
SignatureVerification: t.SignatureVerification,
|
SignatureVerification: t.SignatureVerification,
|
||||||
TrustedIdentities: append([]string(nil), t.TrustedIdentities...),
|
TrustedIdentities: append([]string(nil), t.TrustedIdentities...),
|
||||||
|
@ -180,15 +152,15 @@ func (t *OCITrustPolicy) clone() *OCITrustPolicy {
|
||||||
|
|
||||||
// validateRegistryScopes validates if the policy document is following the
|
// validateRegistryScopes validates if the policy document is following the
|
||||||
// Notary Project spec rules for registry scopes
|
// Notary Project spec rules for registry scopes
|
||||||
func validateRegistryScopes(policyDoc *OCIDocument) error {
|
func validateRegistryScopes(policyDoc *Document) error {
|
||||||
registryScopeCount := make(map[string]int)
|
registryScopeCount := make(map[string]int)
|
||||||
for _, statement := range policyDoc.TrustPolicies {
|
for _, statement := range policyDoc.TrustPolicies {
|
||||||
// Verify registry scopes are valid
|
// Verify registry scopes are valid
|
||||||
if len(statement.RegistryScopes) == 0 {
|
if len(statement.RegistryScopes) == 0 {
|
||||||
return fmt.Errorf("oci trust policy statement %q has zero registry scopes, it must specify registry scopes with at least one value", statement.Name)
|
return fmt.Errorf("trust policy statement %q has zero registry scopes, it must specify registry scopes with at least one value", statement.Name)
|
||||||
}
|
}
|
||||||
if len(statement.RegistryScopes) > 1 && slices.Contains(statement.RegistryScopes, trustpolicy.Wildcard) {
|
if len(statement.RegistryScopes) > 1 && slices.Contains(statement.RegistryScopes, trustpolicy.Wildcard) {
|
||||||
return fmt.Errorf("oci trust policy statement %q uses wildcard registry scope '*', a wildcard scope cannot be used in conjunction with other scope values", statement.Name)
|
return fmt.Errorf("trust policy statement %q uses wildcard registry scope '*', a wildcard scope cannot be used in conjunction with other scope values", statement.Name)
|
||||||
}
|
}
|
||||||
for _, scope := range statement.RegistryScopes {
|
for _, scope := range statement.RegistryScopes {
|
||||||
if scope != trustpolicy.Wildcard {
|
if scope != trustpolicy.Wildcard {
|
||||||
|
@ -203,7 +175,7 @@ func validateRegistryScopes(policyDoc *OCIDocument) error {
|
||||||
// Verify one policy statement per registry scope
|
// Verify one policy statement per registry scope
|
||||||
for key := range registryScopeCount {
|
for key := range registryScopeCount {
|
||||||
if registryScopeCount[key] > 1 {
|
if registryScopeCount[key] > 1 {
|
||||||
return fmt.Errorf("registry scope %q is present in multiple oci trust policy statements, one registry scope value can only be associated with one statement", key)
|
return fmt.Errorf("registry scope %q is present in multiple trust policy statements, one registry scope value can only be associated with one statement", key)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -218,6 +190,7 @@ func getArtifactPathFromReference(artifactReference string) (string, error) {
|
||||||
if i < 0 {
|
if i < 0 {
|
||||||
return "", fmt.Errorf("artifact URI %q could not be parsed, make sure it is the fully qualified oci artifact URI without the scheme/protocol. e.g domain.com:80/my/repository@sha256:digest", artifactReference)
|
return "", fmt.Errorf("artifact URI %q could not be parsed, make sure it is the fully qualified oci artifact URI without the scheme/protocol. e.g domain.com:80/my/repository@sha256:digest", artifactReference)
|
||||||
}
|
}
|
||||||
|
|
||||||
artifactPath := artifactReference[:i]
|
artifactPath := artifactReference[:i]
|
||||||
if err := validateRegistryScopeFormat(artifactPath); err != nil {
|
if err := validateRegistryScopeFormat(artifactPath); err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
|
@ -241,10 +214,12 @@ func validateRegistryScopeFormat(scope string) error {
|
||||||
if len(scope) > 1 && strings.Contains(scope, "*") {
|
if len(scope) > 1 && strings.Contains(scope, "*") {
|
||||||
return fmt.Errorf(errorWildCardMessage, scope)
|
return fmt.Errorf(errorWildCardMessage, scope)
|
||||||
}
|
}
|
||||||
|
|
||||||
domain, repository, found := strings.Cut(scope, "/")
|
domain, repository, found := strings.Cut(scope, "/")
|
||||||
if !found {
|
if !found {
|
||||||
return fmt.Errorf(errorMessage, scope)
|
return fmt.Errorf(errorMessage, scope)
|
||||||
}
|
}
|
||||||
|
|
||||||
if domain == "" || repository == "" || !domainRegexp.MatchString(domain) || !repositoryRegexp.MatchString(repository) {
|
if domain == "" || repository == "" || !domainRegexp.MatchString(domain) || !repositoryRegexp.MatchString(repository) {
|
||||||
return fmt.Errorf(errorMessage, scope)
|
return fmt.Errorf(errorMessage, scope)
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,47 +23,32 @@ import (
|
||||||
"github.com/notaryproject/notation-go/dir"
|
"github.com/notaryproject/notation-go/dir"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestLoadOCIDocumentFromOldFileLocation(t *testing.T) {
|
func TestLoadDocumentFromFileLocation(t *testing.T) {
|
||||||
tempRoot := t.TempDir()
|
tempRoot := t.TempDir()
|
||||||
dir.UserConfigDir = tempRoot
|
dir.UserConfigDir = tempRoot
|
||||||
path := filepath.Join(tempRoot, "trustpolicy.json")
|
path := filepath.Join(tempRoot, "trustpolicy.json")
|
||||||
policyJson, _ := json.Marshal(dummyOCIPolicyDocument())
|
policyJson, _ := json.Marshal(dummyPolicyDocument())
|
||||||
if err := os.WriteFile(path, policyJson, 0600); err != nil {
|
if err := os.WriteFile(path, policyJson, 0600); err != nil {
|
||||||
t.Fatalf("TestLoadOCIDocument write policy file failed. Error: %v", err)
|
t.Fatalf("TestLoadDocument write policy file failed. Error: %v", err)
|
||||||
}
|
}
|
||||||
t.Cleanup(func() { os.RemoveAll(tempRoot) })
|
t.Cleanup(func() { os.RemoveAll(tempRoot) })
|
||||||
|
|
||||||
if _, err := LoadOCIDocument(); err != nil {
|
if _, err := LoadDocument(); err != nil {
|
||||||
t.Fatalf("LoadOCIDocument() should not throw error for an existing policy file. Error: %v", err)
|
t.Fatalf("LoadDocument() should not throw error for an existing policy file. Error: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestLoadOCIDocumentFromNewFileLocation(t *testing.T) {
|
func TestLoadDocumentError(t *testing.T) {
|
||||||
tempRoot := t.TempDir()
|
tempRoot := t.TempDir()
|
||||||
dir.UserConfigDir = tempRoot
|
dir.UserConfigDir = tempRoot
|
||||||
path := filepath.Join(tempRoot, "trustpolicy.oci.json")
|
if _, err := LoadDocument(); err == nil {
|
||||||
policyJson, _ := json.Marshal(dummyOCIPolicyDocument())
|
t.Fatalf("LoadDocument() should throw error if trust policy is not found")
|
||||||
if err := os.WriteFile(path, policyJson, 0600); err != nil {
|
|
||||||
t.Fatalf("TestLoadOCIDocument write policy file failed. Error: %v", err)
|
|
||||||
}
|
|
||||||
t.Cleanup(func() { os.RemoveAll(tempRoot) })
|
|
||||||
|
|
||||||
if _, err := LoadOCIDocument(); err != nil {
|
|
||||||
t.Fatalf("LoadOCIDocument() should not throw error for an existing policy file. Error: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestLoadOCIDocumentError(t *testing.T) {
|
|
||||||
tempRoot := t.TempDir()
|
|
||||||
dir.UserConfigDir = tempRoot
|
|
||||||
if _, err := LoadOCIDocument(); err == nil {
|
|
||||||
t.Fatalf("LoadOCIDocument() should throw error if OCI trust policy is not found")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestApplicableTrustPolicy tests filtering policies against registry scopes
|
// TestApplicableTrustPolicy tests filtering policies against registry scopes
|
||||||
func TestApplicableTrustPolicy(t *testing.T) {
|
func TestApplicableTrustPolicy(t *testing.T) {
|
||||||
policyDoc := dummyOCIPolicyDocument()
|
policyDoc := dummyPolicyDocument()
|
||||||
|
|
||||||
policyStatement := policyDoc.TrustPolicies[0]
|
policyStatement := policyDoc.TrustPolicies[0]
|
||||||
policyStatement.Name = "test-statement-name-1"
|
policyStatement.Name = "test-statement-name-1"
|
||||||
|
@ -72,7 +57,7 @@ func TestApplicableTrustPolicy(t *testing.T) {
|
||||||
policyStatement.RegistryScopes = []string{registryScope}
|
policyStatement.RegistryScopes = []string{registryScope}
|
||||||
policyStatement.SignatureVerification = SignatureVerification{VerificationLevel: "strict"}
|
policyStatement.SignatureVerification = SignatureVerification{VerificationLevel: "strict"}
|
||||||
|
|
||||||
policyDoc.TrustPolicies = []OCITrustPolicy{
|
policyDoc.TrustPolicies = []TrustPolicy{
|
||||||
policyStatement,
|
policyStatement,
|
||||||
}
|
}
|
||||||
// existing Registry Scope
|
// existing Registry Scope
|
||||||
|
@ -83,12 +68,12 @@ func TestApplicableTrustPolicy(t *testing.T) {
|
||||||
|
|
||||||
// non-existing Registry Scope
|
// non-existing Registry Scope
|
||||||
policy, err = (&policyDoc).GetApplicableTrustPolicy("non.existing.scope/repo@sha256:hash")
|
policy, err = (&policyDoc).GetApplicableTrustPolicy("non.existing.scope/repo@sha256:hash")
|
||||||
if policy != nil || err == nil || err.Error() != "artifact \"non.existing.scope/repo@sha256:hash\" has no applicable oci trust policy statement. Trust policy applicability for a given artifact is determined by registryScopes. To create a trust policy, see: https://notaryproject.dev/docs/quickstart/#create-a-trust-policy" {
|
if policy != nil || err == nil || err.Error() != "artifact \"non.existing.scope/repo@sha256:hash\" has no applicable trust policy statement. Trust policy applicability for a given artifact is determined by registryScopes. To create a trust policy, see: https://notaryproject.dev/docs/quickstart/#create-a-trust-policy" {
|
||||||
t.Fatalf("GetApplicableTrustPolicy() should return nil for non existing registry scope")
|
t.Fatalf("GetApplicableTrustPolicy() should return nil for non existing registry scope")
|
||||||
}
|
}
|
||||||
|
|
||||||
// wildcard registry scope
|
// wildcard registry scope
|
||||||
wildcardStatement := OCITrustPolicy{
|
wildcardStatement := TrustPolicy{
|
||||||
Name: "test-statement-name-2",
|
Name: "test-statement-name-2",
|
||||||
SignatureVerification: SignatureVerification{VerificationLevel: "skip"},
|
SignatureVerification: SignatureVerification{VerificationLevel: "skip"},
|
||||||
TrustStores: []string{},
|
TrustStores: []string{},
|
||||||
|
@ -96,7 +81,7 @@ func TestApplicableTrustPolicy(t *testing.T) {
|
||||||
RegistryScopes: []string{"*"},
|
RegistryScopes: []string{"*"},
|
||||||
}
|
}
|
||||||
|
|
||||||
policyDoc.TrustPolicies = []OCITrustPolicy{
|
policyDoc.TrustPolicies = []TrustPolicy{
|
||||||
policyStatement,
|
policyStatement,
|
||||||
wildcardStatement,
|
wildcardStatement,
|
||||||
}
|
}
|
||||||
|
@ -110,130 +95,130 @@ func TestApplicableTrustPolicy(t *testing.T) {
|
||||||
// and tests various validations on policy elements
|
// and tests various validations on policy elements
|
||||||
func TestValidateInvalidPolicyDocument(t *testing.T) {
|
func TestValidateInvalidPolicyDocument(t *testing.T) {
|
||||||
// Sanity check
|
// Sanity check
|
||||||
var nilPolicyDoc *OCIDocument
|
var nilPolicyDoc *Document
|
||||||
err := nilPolicyDoc.Validate()
|
err := nilPolicyDoc.Validate()
|
||||||
if err == nil || err.Error() != "oci trust policy document cannot be nil" {
|
if err == nil || err.Error() != "trust policy document cannot be nil" {
|
||||||
t.Fatalf("nil policyDoc should return error")
|
t.Fatalf("nil policyDoc should return error")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Invalid Version
|
// Invalid Version
|
||||||
policyDoc := dummyOCIPolicyDocument()
|
policyDoc := dummyPolicyDocument()
|
||||||
policyDoc.Version = "invalid"
|
policyDoc.Version = "invalid"
|
||||||
err = policyDoc.Validate()
|
err = policyDoc.Validate()
|
||||||
if err == nil || err.Error() != "oci trust policy document uses unsupported version \"invalid\"" {
|
if err == nil || err.Error() != "trust policy document uses unsupported version \"invalid\"" {
|
||||||
t.Fatalf("invalid version should return error")
|
t.Fatalf("invalid version should return error")
|
||||||
}
|
}
|
||||||
|
|
||||||
// No Policy Statements
|
// No Policy Statements
|
||||||
policyDoc = dummyOCIPolicyDocument()
|
policyDoc = dummyPolicyDocument()
|
||||||
policyDoc.TrustPolicies = nil
|
policyDoc.TrustPolicies = nil
|
||||||
err = policyDoc.Validate()
|
err = policyDoc.Validate()
|
||||||
if err == nil || err.Error() != "oci trust policy document can not have zero trust policy statements" {
|
if err == nil || err.Error() != "trust policy document can not have zero trust policy statements" {
|
||||||
t.Fatalf("zero policy statements should return error")
|
t.Fatalf("zero policy statements should return error")
|
||||||
}
|
}
|
||||||
|
|
||||||
// No Policy Statement Name
|
// No Policy Statement Name
|
||||||
policyDoc = dummyOCIPolicyDocument()
|
policyDoc = dummyPolicyDocument()
|
||||||
policyDoc.TrustPolicies[0].Name = ""
|
policyDoc.TrustPolicies[0].Name = ""
|
||||||
err = policyDoc.Validate()
|
err = policyDoc.Validate()
|
||||||
if err == nil || err.Error() != "oci trust policy: a trust policy statement is missing a name, every statement requires a name" {
|
if err == nil || err.Error() != "trust policy: a trust policy statement is missing a name, every statement requires a name" {
|
||||||
t.Fatalf("policy statement with no name should return an error")
|
t.Fatalf("policy statement with no name should return an error")
|
||||||
}
|
}
|
||||||
|
|
||||||
// No Registry Scopes
|
// No Registry Scopes
|
||||||
policyDoc = dummyOCIPolicyDocument()
|
policyDoc = dummyPolicyDocument()
|
||||||
policyDoc.TrustPolicies[0].RegistryScopes = nil
|
policyDoc.TrustPolicies[0].RegistryScopes = nil
|
||||||
err = policyDoc.Validate()
|
err = policyDoc.Validate()
|
||||||
if err == nil || err.Error() != "oci trust policy statement \"test-statement-name\" has zero registry scopes, it must specify registry scopes with at least one value" {
|
if err == nil || err.Error() != "trust policy statement \"test-statement-name\" has zero registry scopes, it must specify registry scopes with at least one value" {
|
||||||
t.Fatalf("policy statement with registry scopes should return error")
|
t.Fatalf("policy statement with registry scopes should return error")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Multiple policy statements with same registry scope
|
// Multiple policy statements with same registry scope
|
||||||
policyDoc = dummyOCIPolicyDocument()
|
policyDoc = dummyPolicyDocument()
|
||||||
policyStatement1 := policyDoc.TrustPolicies[0].clone()
|
policyStatement1 := policyDoc.TrustPolicies[0].clone()
|
||||||
policyStatement2 := policyDoc.TrustPolicies[0].clone()
|
policyStatement2 := policyDoc.TrustPolicies[0].clone()
|
||||||
policyStatement2.Name = "test-statement-name-2"
|
policyStatement2.Name = "test-statement-name-2"
|
||||||
policyDoc.TrustPolicies = []OCITrustPolicy{*policyStatement1, *policyStatement2}
|
policyDoc.TrustPolicies = []TrustPolicy{*policyStatement1, *policyStatement2}
|
||||||
err = policyDoc.Validate()
|
err = policyDoc.Validate()
|
||||||
if err == nil || err.Error() != "registry scope \"registry.acme-rockets.io/software/net-monitor\" is present in multiple oci trust policy statements, one registry scope value can only be associated with one statement" {
|
if err == nil || err.Error() != "registry scope \"registry.acme-rockets.io/software/net-monitor\" is present in multiple trust policy statements, one registry scope value can only be associated with one statement" {
|
||||||
t.Fatalf("Policy statements with same registry scope should return error %q", err)
|
t.Fatalf("Policy statements with same registry scope should return error %q", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Registry scopes with a wildcard
|
// Registry scopes with a wildcard
|
||||||
policyDoc = dummyOCIPolicyDocument()
|
policyDoc = dummyPolicyDocument()
|
||||||
policyDoc.TrustPolicies[0].RegistryScopes = []string{"*", "registry.acme-rockets.io/software/net-monitor"}
|
policyDoc.TrustPolicies[0].RegistryScopes = []string{"*", "registry.acme-rockets.io/software/net-monitor"}
|
||||||
err = policyDoc.Validate()
|
err = policyDoc.Validate()
|
||||||
if err == nil || err.Error() != "oci trust policy statement \"test-statement-name\" uses wildcard registry scope '*', a wildcard scope cannot be used in conjunction with other scope values" {
|
if err == nil || err.Error() != "trust policy statement \"test-statement-name\" uses wildcard registry scope '*', a wildcard scope cannot be used in conjunction with other scope values" {
|
||||||
t.Fatalf("policy statement with more than a wildcard registry scope should return error")
|
t.Fatalf("policy statement with more than a wildcard registry scope should return error")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Invalid SignatureVerification
|
// Invalid SignatureVerification
|
||||||
policyDoc = dummyOCIPolicyDocument()
|
policyDoc = dummyPolicyDocument()
|
||||||
policyDoc.TrustPolicies[0].SignatureVerification = SignatureVerification{VerificationLevel: "invalid"}
|
policyDoc.TrustPolicies[0].SignatureVerification = SignatureVerification{VerificationLevel: "invalid"}
|
||||||
err = policyDoc.Validate()
|
err = policyDoc.Validate()
|
||||||
if err == nil || err.Error() != "oci trust policy: trust policy statement \"test-statement-name\" has invalid signatureVerification: invalid signature verification level \"invalid\"" {
|
if err == nil || err.Error() != "trust policy: trust policy statement \"test-statement-name\" has invalid signatureVerification: invalid signature verification level \"invalid\"" {
|
||||||
t.Fatalf("policy statement with invalid SignatureVerification should return error")
|
t.Fatalf("policy statement with invalid SignatureVerification should return error")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Invalid SignatureVerification VerifyTimestamp
|
// Invalid SignatureVerification VerifyTimestamp
|
||||||
policyDoc = dummyOCIPolicyDocument()
|
policyDoc = dummyPolicyDocument()
|
||||||
policyDoc.TrustPolicies[0].SignatureVerification.VerifyTimestamp = "invalid"
|
policyDoc.TrustPolicies[0].SignatureVerification.VerifyTimestamp = "invalid"
|
||||||
expectedErrMsg := "oci trust policy: trust policy statement \"test-statement-name\" has invalid signatureVerification: verifyTimestamp must be \"always\" or \"afterCertExpiry\", but got \"invalid\""
|
expectedErrMsg := "trust policy: trust policy statement \"test-statement-name\" has invalid signatureVerification: verifyTimestamp must be \"always\" or \"afterCertExpiry\", but got \"invalid\""
|
||||||
err = policyDoc.Validate()
|
err = policyDoc.Validate()
|
||||||
if err == nil || err.Error() != expectedErrMsg {
|
if err == nil || err.Error() != expectedErrMsg {
|
||||||
t.Fatalf("expected %s, but got %s", expectedErrMsg, err)
|
t.Fatalf("expected %s, but got %s", expectedErrMsg, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// strict SignatureVerification should have a trust store
|
// strict SignatureVerification should have a trust store
|
||||||
policyDoc = dummyOCIPolicyDocument()
|
policyDoc = dummyPolicyDocument()
|
||||||
policyDoc.TrustPolicies[0].TrustStores = []string{}
|
policyDoc.TrustPolicies[0].TrustStores = []string{}
|
||||||
err = policyDoc.Validate()
|
err = policyDoc.Validate()
|
||||||
if err == nil || err.Error() != "oci trust policy: trust policy statement \"test-statement-name\" is either missing trust stores or trusted identities, both must be specified" {
|
if err == nil || err.Error() != "trust policy: trust policy statement \"test-statement-name\" is either missing trust stores or trusted identities, both must be specified" {
|
||||||
t.Fatalf("strict SignatureVerification should have a trust store")
|
t.Fatalf("strict SignatureVerification should have a trust store")
|
||||||
}
|
}
|
||||||
|
|
||||||
// strict SignatureVerification should have trusted identities
|
// strict SignatureVerification should have trusted identities
|
||||||
policyDoc = dummyOCIPolicyDocument()
|
policyDoc = dummyPolicyDocument()
|
||||||
policyDoc.TrustPolicies[0].TrustedIdentities = []string{}
|
policyDoc.TrustPolicies[0].TrustedIdentities = []string{}
|
||||||
err = policyDoc.Validate()
|
err = policyDoc.Validate()
|
||||||
if err == nil || err.Error() != "oci trust policy: trust policy statement \"test-statement-name\" is either missing trust stores or trusted identities, both must be specified" {
|
if err == nil || err.Error() != "trust policy: trust policy statement \"test-statement-name\" is either missing trust stores or trusted identities, both must be specified" {
|
||||||
t.Fatalf("strict SignatureVerification should have trusted identities")
|
t.Fatalf("strict SignatureVerification should have trusted identities")
|
||||||
}
|
}
|
||||||
|
|
||||||
// skip SignatureVerification should not have trust store or trusted identities
|
// skip SignatureVerification should not have trust store or trusted identities
|
||||||
policyDoc = dummyOCIPolicyDocument()
|
policyDoc = dummyPolicyDocument()
|
||||||
policyDoc.TrustPolicies[0].SignatureVerification = SignatureVerification{VerificationLevel: "skip"}
|
policyDoc.TrustPolicies[0].SignatureVerification = SignatureVerification{VerificationLevel: "skip"}
|
||||||
err = policyDoc.Validate()
|
err = policyDoc.Validate()
|
||||||
if err == nil || err.Error() != "oci trust policy: trust policy statement \"test-statement-name\" is set to skip signature verification but configured with trust stores and/or trusted identities, remove them if signature verification needs to be skipped" {
|
if err == nil || err.Error() != "trust policy: trust policy statement \"test-statement-name\" is set to skip signature verification but configured with trust stores and/or trusted identities, remove them if signature verification needs to be skipped" {
|
||||||
t.Fatalf("strict SignatureVerification should have trusted identities")
|
t.Fatalf("strict SignatureVerification should have trusted identities")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Empty Trusted Identity should throw error
|
// Empty Trusted Identity should throw error
|
||||||
policyDoc = dummyOCIPolicyDocument()
|
policyDoc = dummyPolicyDocument()
|
||||||
policyDoc.TrustPolicies[0].TrustedIdentities = []string{""}
|
policyDoc.TrustPolicies[0].TrustedIdentities = []string{""}
|
||||||
err = policyDoc.Validate()
|
err = policyDoc.Validate()
|
||||||
if err == nil || err.Error() != "oci trust policy: trust policy statement \"test-statement-name\" has an empty trusted identity" {
|
if err == nil || err.Error() != "trust policy: trust policy statement \"test-statement-name\" has an empty trusted identity" {
|
||||||
t.Fatalf("policy statement with empty trusted identity should return error")
|
t.Fatalf("policy statement with empty trusted identity should return error")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Trusted Identity without separator should throw error
|
// Trusted Identity without separator should throw error
|
||||||
policyDoc = dummyOCIPolicyDocument()
|
policyDoc = dummyPolicyDocument()
|
||||||
policyDoc.TrustPolicies[0].TrustedIdentities = []string{"x509.subject"}
|
policyDoc.TrustPolicies[0].TrustedIdentities = []string{"x509.subject"}
|
||||||
err = policyDoc.Validate()
|
err = policyDoc.Validate()
|
||||||
if err == nil || err.Error() != "oci trust policy: trust policy statement \"test-statement-name\" has trusted identity \"x509.subject\" missing separator" {
|
if err == nil || err.Error() != "trust policy: trust policy statement \"test-statement-name\" has trusted identity \"x509.subject\" missing separator" {
|
||||||
t.Fatalf("policy statement with trusted identity missing separator should return error")
|
t.Fatalf("policy statement with trusted identity missing separator should return error")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Empty Trusted Identity value should throw error
|
// Empty Trusted Identity value should throw error
|
||||||
policyDoc = dummyOCIPolicyDocument()
|
policyDoc = dummyPolicyDocument()
|
||||||
policyDoc.TrustPolicies[0].TrustedIdentities = []string{"x509.subject:"}
|
policyDoc.TrustPolicies[0].TrustedIdentities = []string{"x509.subject:"}
|
||||||
err = policyDoc.Validate()
|
err = policyDoc.Validate()
|
||||||
if err == nil || err.Error() != "oci trust policy: trust policy statement \"test-statement-name\" has trusted identity \"x509.subject:\" without an identity value" {
|
if err == nil || err.Error() != "trust policy: trust policy statement \"test-statement-name\" has trusted identity \"x509.subject:\" without an identity value" {
|
||||||
t.Fatalf("policy statement with trusted identity missing identity value should return error")
|
t.Fatalf("policy statement with trusted identity missing identity value should return error")
|
||||||
}
|
}
|
||||||
|
|
||||||
// trust store/trusted identities are optional for skip SignatureVerification
|
// trust store/trusted identities are optional for skip SignatureVerification
|
||||||
policyDoc = dummyOCIPolicyDocument()
|
policyDoc = dummyPolicyDocument()
|
||||||
policyDoc.TrustPolicies[0].SignatureVerification = SignatureVerification{VerificationLevel: "skip"}
|
policyDoc.TrustPolicies[0].SignatureVerification = SignatureVerification{VerificationLevel: "skip"}
|
||||||
policyDoc.TrustPolicies[0].TrustStores = []string{}
|
policyDoc.TrustPolicies[0].TrustStores = []string{}
|
||||||
policyDoc.TrustPolicies[0].TrustedIdentities = []string{}
|
policyDoc.TrustPolicies[0].TrustedIdentities = []string{}
|
||||||
|
@ -243,52 +228,52 @@ func TestValidateInvalidPolicyDocument(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Trust Store missing separator
|
// Trust Store missing separator
|
||||||
policyDoc = dummyOCIPolicyDocument()
|
policyDoc = dummyPolicyDocument()
|
||||||
policyDoc.TrustPolicies[0].TrustStores = []string{"ca"}
|
policyDoc.TrustPolicies[0].TrustStores = []string{"ca"}
|
||||||
err = policyDoc.Validate()
|
err = policyDoc.Validate()
|
||||||
if err == nil || err.Error() != "oci trust policy: trust policy statement \"test-statement-name\" has malformed trust store value \"ca\". The required format is <TrustStoreType>:<TrustStoreName>" {
|
if err == nil || err.Error() != "trust policy: trust policy statement \"test-statement-name\" has malformed trust store value \"ca\". The required format is <TrustStoreType>:<TrustStoreName>" {
|
||||||
t.Fatalf("policy statement with trust store missing separator should return error")
|
t.Fatalf("policy statement with trust store missing separator should return error")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Invalid Trust Store type
|
// Invalid Trust Store type
|
||||||
policyDoc = dummyOCIPolicyDocument()
|
policyDoc = dummyPolicyDocument()
|
||||||
policyDoc.TrustPolicies[0].TrustStores = []string{"invalid:test-trust-store"}
|
policyDoc.TrustPolicies[0].TrustStores = []string{"invalid:test-trust-store"}
|
||||||
err = policyDoc.Validate()
|
err = policyDoc.Validate()
|
||||||
if err == nil || err.Error() != "oci trust policy: trust policy statement \"test-statement-name\" uses an unsupported trust store type \"invalid\" in trust store value \"invalid:test-trust-store\"" {
|
if err == nil || err.Error() != "trust policy: trust policy statement \"test-statement-name\" uses an unsupported trust store type \"invalid\" in trust store value \"invalid:test-trust-store\"" {
|
||||||
t.Fatalf("policy statement with invalid trust store type should return error")
|
t.Fatalf("policy statement with invalid trust store type should return error")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Empty Named Store
|
// Empty Named Store
|
||||||
policyDoc = dummyOCIPolicyDocument()
|
policyDoc = dummyPolicyDocument()
|
||||||
policyDoc.TrustPolicies[0].TrustStores = []string{"ca:"}
|
policyDoc.TrustPolicies[0].TrustStores = []string{"ca:"}
|
||||||
err = policyDoc.Validate()
|
err = policyDoc.Validate()
|
||||||
if err == nil || err.Error() != "oci trust policy: trust policy statement \"test-statement-name\" uses an unsupported trust store name \"\" in trust store value \"ca:\". Named store name needs to follow [a-zA-Z0-9_.-]+ format" {
|
if err == nil || err.Error() != "trust policy: trust policy statement \"test-statement-name\" uses an unsupported trust store name \"\" in trust store value \"ca:\". Named store name needs to follow [a-zA-Z0-9_.-]+ format" {
|
||||||
t.Fatalf("policy statement with trust store missing named store should return error")
|
t.Fatalf("policy statement with trust store missing named store should return error")
|
||||||
}
|
}
|
||||||
|
|
||||||
// trusted identities with a wildcard
|
// trusted identities with a wildcard
|
||||||
policyDoc = dummyOCIPolicyDocument()
|
policyDoc = dummyPolicyDocument()
|
||||||
policyDoc.TrustPolicies[0].TrustedIdentities = []string{"*", "test-identity"}
|
policyDoc.TrustPolicies[0].TrustedIdentities = []string{"*", "test-identity"}
|
||||||
err = policyDoc.Validate()
|
err = policyDoc.Validate()
|
||||||
if err == nil || err.Error() != "oci trust policy: trust policy statement \"test-statement-name\" uses a wildcard trusted identity '*', a wildcard identity cannot be used in conjunction with other values" {
|
if err == nil || err.Error() != "trust policy: trust policy statement \"test-statement-name\" uses a wildcard trusted identity '*', a wildcard identity cannot be used in conjunction with other values" {
|
||||||
t.Fatalf("policy statement with more than a wildcard trusted identity should return error")
|
t.Fatalf("policy statement with more than a wildcard trusted identity should return error")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Policy Document with duplicate policy statement names
|
// Policy Document with duplicate policy statement names
|
||||||
policyDoc = dummyOCIPolicyDocument()
|
policyDoc = dummyPolicyDocument()
|
||||||
policyStatement1 = policyDoc.TrustPolicies[0].clone()
|
policyStatement1 = policyDoc.TrustPolicies[0].clone()
|
||||||
policyStatement2 = policyDoc.TrustPolicies[0].clone()
|
policyStatement2 = policyDoc.TrustPolicies[0].clone()
|
||||||
policyStatement2.RegistryScopes = []string{"registry.acme-rockets.io/software/legacy/metrics"}
|
policyStatement2.RegistryScopes = []string{"registry.acme-rockets.io/software/legacy/metrics"}
|
||||||
policyDoc.TrustPolicies = []OCITrustPolicy{*policyStatement1, *policyStatement2}
|
policyDoc.TrustPolicies = []TrustPolicy{*policyStatement1, *policyStatement2}
|
||||||
err = policyDoc.Validate()
|
err = policyDoc.Validate()
|
||||||
if err == nil || err.Error() != "multiple oci trust policy statements use the same name \"test-statement-name\", statement names must be unique" {
|
if err == nil || err.Error() != "multiple trust policy statements use the same name \"test-statement-name\", statement names must be unique" {
|
||||||
t.Fatalf("policy statements with same name should return error")
|
t.Fatalf("policy statements with same name should return error")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestValidRegistryScopes tests valid scopes are accepted
|
// TestValidRegistryScopes tests valid scopes are accepted
|
||||||
func TestValidRegistryScopes(t *testing.T) {
|
func TestValidRegistryScopes(t *testing.T) {
|
||||||
policyDoc := dummyOCIPolicyDocument()
|
policyDoc := dummyPolicyDocument()
|
||||||
validScopes := []string{
|
validScopes := []string{
|
||||||
"*", "example.com/rep", "example.com:8080/rep/rep2", "example.com/rep/subrep/subsub",
|
"*", "example.com/rep", "example.com:8080/rep/rep2", "example.com/rep/subrep/subsub",
|
||||||
"10.10.10.10:8080/rep/rep2", "domain/rep", "domain:1234/rep",
|
"10.10.10.10:8080/rep/rep2", "domain/rep", "domain:1234/rep",
|
||||||
|
@ -305,7 +290,7 @@ func TestValidRegistryScopes(t *testing.T) {
|
||||||
|
|
||||||
// TestInvalidRegistryScopes tests invalid scopes are rejected
|
// TestInvalidRegistryScopes tests invalid scopes are rejected
|
||||||
func TestInvalidRegistryScopes(t *testing.T) {
|
func TestInvalidRegistryScopes(t *testing.T) {
|
||||||
policyDoc := dummyOCIPolicyDocument()
|
policyDoc := dummyPolicyDocument()
|
||||||
invalidScopes := []string{
|
invalidScopes := []string{
|
||||||
"", "1:1", "a,b", "abcd", "1111", "1,2", "example.com/rep:tag",
|
"", "1:1", "a,b", "abcd", "1111", "1,2", "example.com/rep:tag",
|
||||||
"example.com/rep/subrep/sub:latest", "example.com", "rep/rep2:latest",
|
"example.com/rep/subrep/sub:latest", "example.com", "rep/rep2:latest",
|
||||||
|
@ -333,7 +318,7 @@ func TestInvalidRegistryScopes(t *testing.T) {
|
||||||
|
|
||||||
// TestValidateValidPolicyDocument tests a happy policy document
|
// TestValidateValidPolicyDocument tests a happy policy document
|
||||||
func TestValidateValidPolicyDocument(t *testing.T) {
|
func TestValidateValidPolicyDocument(t *testing.T) {
|
||||||
policyDoc := dummyOCIPolicyDocument()
|
policyDoc := dummyPolicyDocument()
|
||||||
|
|
||||||
policyStatement1 := policyDoc.TrustPolicies[0].clone()
|
policyStatement1 := policyDoc.TrustPolicies[0].clone()
|
||||||
|
|
||||||
|
@ -376,7 +361,7 @@ func TestValidateValidPolicyDocument(t *testing.T) {
|
||||||
policyStatement8.RegistryScopes = []string{"registry.acme-rockets.io/software/net-monitor8"}
|
policyStatement8.RegistryScopes = []string{"registry.acme-rockets.io/software/net-monitor8"}
|
||||||
policyStatement8.SignatureVerification.VerifyTimestamp = OptionAfterCertExpiry
|
policyStatement8.SignatureVerification.VerifyTimestamp = OptionAfterCertExpiry
|
||||||
|
|
||||||
policyDoc.TrustPolicies = []OCITrustPolicy{
|
policyDoc.TrustPolicies = []TrustPolicy{
|
||||||
*policyStatement1,
|
*policyStatement1,
|
||||||
*policyStatement2,
|
*policyStatement2,
|
||||||
*policyStatement3,
|
*policyStatement3,
|
||||||
|
|
|
@ -158,9 +158,8 @@ func (e errPolicyNotExist) Error() string {
|
||||||
return fmt.Sprintf("trust policy is not present. To create a trust policy, see: %s", trustPolicyLink)
|
return fmt.Sprintf("trust policy is not present. To create a trust policy, see: %s", trustPolicyLink)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetVerificationLevel returns [VerificationLevel] for the given
|
// GetVerificationLevel returns VerificationLevel struct for the given
|
||||||
// [SignatureVerification] struct.
|
// SignatureVerification struct throws error if SignatureVerification is invalid
|
||||||
// It throws error if SignatureVerification is invalid.
|
|
||||||
func (signatureVerification *SignatureVerification) GetVerificationLevel() (*VerificationLevel, error) {
|
func (signatureVerification *SignatureVerification) GetVerificationLevel() (*VerificationLevel, error) {
|
||||||
if signatureVerification.VerificationLevel == "" {
|
if signatureVerification.VerificationLevel == "" {
|
||||||
return nil, errors.New("signature verification level is empty or missing in the trust policy statement")
|
return nil, errors.New("signature verification level is empty or missing in the trust policy statement")
|
||||||
|
@ -175,13 +174,16 @@ func (signatureVerification *SignatureVerification) GetVerificationLevel() (*Ver
|
||||||
if baseLevel == nil {
|
if baseLevel == nil {
|
||||||
return nil, fmt.Errorf("invalid signature verification level %q", signatureVerification.VerificationLevel)
|
return nil, fmt.Errorf("invalid signature verification level %q", signatureVerification.VerificationLevel)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(signatureVerification.Override) == 0 {
|
if len(signatureVerification.Override) == 0 {
|
||||||
// nothing to override, return the base verification level
|
// nothing to override, return the base verification level
|
||||||
return baseLevel, nil
|
return baseLevel, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if baseLevel == LevelSkip {
|
if baseLevel == LevelSkip {
|
||||||
return nil, fmt.Errorf("signature verification level %q can't be used to customize signature verification", baseLevel.Name)
|
return nil, fmt.Errorf("signature verification level %q can't be used to customize signature verification", baseLevel.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
customVerificationLevel := &VerificationLevel{
|
customVerificationLevel := &VerificationLevel{
|
||||||
Name: "custom",
|
Name: "custom",
|
||||||
Enforcement: make(map[ValidationType]ValidationAction),
|
Enforcement: make(map[ValidationType]ValidationAction),
|
||||||
|
@ -216,11 +218,13 @@ func (signatureVerification *SignatureVerification) GetVerificationLevel() (*Ver
|
||||||
if validationAction == "" {
|
if validationAction == "" {
|
||||||
return nil, fmt.Errorf("verification action %q in custom signature verification is not supported, supported values are %q", value, ValidationActions)
|
return nil, fmt.Errorf("verification action %q in custom signature verification is not supported, supported values are %q", value, ValidationActions)
|
||||||
}
|
}
|
||||||
|
|
||||||
if validationType == TypeIntegrity {
|
if validationType == TypeIntegrity {
|
||||||
return nil, fmt.Errorf("%q verification can not be overridden in custom signature verification", key)
|
return nil, fmt.Errorf("%q verification can not be overridden in custom signature verification", key)
|
||||||
} else if validationType != TypeRevocation && validationAction == ActionSkip {
|
} else if validationType != TypeRevocation && validationAction == ActionSkip {
|
||||||
return nil, fmt.Errorf("%q verification can not be skipped in custom signature verification", key)
|
return nil, fmt.Errorf("%q verification can not be skipped in custom signature verification", key)
|
||||||
}
|
}
|
||||||
|
|
||||||
customVerificationLevel.Enforcement[validationType] = validationAction
|
customVerificationLevel.Enforcement[validationType] = validationAction
|
||||||
}
|
}
|
||||||
return customVerificationLevel, nil
|
return customVerificationLevel, nil
|
||||||
|
@ -240,10 +244,12 @@ func getDocument(path string, v any) error {
|
||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
mode := fileInfo.Mode()
|
mode := fileInfo.Mode()
|
||||||
if mode.IsDir() || mode&fs.ModeSymlink != 0 {
|
if mode.IsDir() || mode&fs.ModeSymlink != 0 {
|
||||||
return fmt.Errorf("trust policy is not a regular file (symlinks are not supported). To create a trust policy, see: %s", trustPolicyLink)
|
return fmt.Errorf("trust policy is not a regular file (symlinks are not supported). To create a trust policy, see: %s", trustPolicyLink)
|
||||||
}
|
}
|
||||||
|
|
||||||
jsonFile, err := os.Open(path)
|
jsonFile, err := os.Open(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, os.ErrPermission) {
|
if errors.Is(err, os.ErrPermission) {
|
||||||
|
@ -316,6 +322,7 @@ func validateTrustStore(policyName string, trustStores []string) error {
|
||||||
return fmt.Errorf("trust policy statement %q uses an unsupported trust store name %q in trust store value %q. Named store name needs to follow [a-zA-Z0-9_.-]+ format", policyName, namedStore, trustStore)
|
return fmt.Errorf("trust policy statement %q uses an unsupported trust store name %q in trust store value %q. Named store name needs to follow [a-zA-Z0-9_.-]+ format", policyName, namedStore, trustStore)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -334,6 +341,7 @@ func validateTrustedIdentities(policyName string, tis []string) error {
|
||||||
if identity == "" {
|
if identity == "" {
|
||||||
return fmt.Errorf("trust policy statement %q has an empty trusted identity", policyName)
|
return fmt.Errorf("trust policy statement %q has an empty trusted identity", policyName)
|
||||||
}
|
}
|
||||||
|
|
||||||
if identity != trustpolicy.Wildcard {
|
if identity != trustpolicy.Wildcard {
|
||||||
identityPrefix, identityValue, found := strings.Cut(identity, ":")
|
identityPrefix, identityValue, found := strings.Cut(identity, ":")
|
||||||
if !found {
|
if !found {
|
||||||
|
@ -372,11 +380,12 @@ func validateOverlappingDNs(policyName string, parsedDNs []parsedDN) error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// isValidTrustStoreType returns true if the given string is a valid
|
// isValidTrustStoreType returns true if the given string is a valid
|
||||||
// [truststore.Type], otherwise false.
|
// truststore.Type, otherwise false.
|
||||||
func isValidTrustStoreType(s string) bool {
|
func isValidTrustStoreType(s string) bool {
|
||||||
for _, p := range truststore.Types {
|
for _, p := range truststore.Types {
|
||||||
if s == string(p) {
|
if s == string(p) {
|
||||||
|
|
|
@ -26,10 +26,10 @@ import (
|
||||||
"github.com/notaryproject/notation-go/dir"
|
"github.com/notaryproject/notation-go/dir"
|
||||||
)
|
)
|
||||||
|
|
||||||
func dummyOCIPolicyDocument() OCIDocument {
|
func dummyPolicyDocument() Document {
|
||||||
return OCIDocument{
|
return Document{
|
||||||
Version: "1.0",
|
Version: "1.0",
|
||||||
TrustPolicies: []OCITrustPolicy{
|
TrustPolicies: []TrustPolicy{
|
||||||
{
|
{
|
||||||
Name: "test-statement-name",
|
Name: "test-statement-name",
|
||||||
RegistryScopes: []string{"registry.acme-rockets.io/software/net-monitor"},
|
RegistryScopes: []string{"registry.acme-rockets.io/software/net-monitor"},
|
||||||
|
@ -41,20 +41,6 @@ func dummyOCIPolicyDocument() OCIDocument {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func dummyBlobPolicyDocument() BlobDocument {
|
|
||||||
return BlobDocument{
|
|
||||||
Version: "1.0",
|
|
||||||
TrustPolicies: []BlobTrustPolicy{
|
|
||||||
{
|
|
||||||
Name: "test-statement-name",
|
|
||||||
SignatureVerification: SignatureVerification{VerificationLevel: "strict"},
|
|
||||||
TrustStores: []string{"ca:valid-trust-store", "signingAuthority:valid-trust-store"},
|
|
||||||
TrustedIdentities: []string{"x509.subject:CN=Notation Test Root,O=Notary,L=Seattle,ST=WA,C=US"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// create testcase for validatePolicyCore method
|
// create testcase for validatePolicyCore method
|
||||||
func TestValidatePolicyCore(t *testing.T) {
|
func TestValidatePolicyCore(t *testing.T) {
|
||||||
policyName := "test-statement-name"
|
policyName := "test-statement-name"
|
||||||
|
@ -306,22 +292,16 @@ func TestGetDocument(t *testing.T) {
|
||||||
t.Skip("skipping test on Windows")
|
t.Skip("skipping test on Windows")
|
||||||
}
|
}
|
||||||
dir.UserConfigDir = "/"
|
dir.UserConfigDir = "/"
|
||||||
var ociDoc OCIDocument
|
var doc Document
|
||||||
var blobDoc BlobDocument
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
expectedDocument any
|
expectedDocument any
|
||||||
actualDocument any
|
actualDocument any
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "valid OCI policy file",
|
name: "valid policy file",
|
||||||
expectedDocument: dummyOCIPolicyDocument(),
|
expectedDocument: dummyPolicyDocument(),
|
||||||
actualDocument: &ociDoc,
|
actualDocument: &doc,
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "valid Blob policy file",
|
|
||||||
expectedDocument: dummyBlobPolicyDocument(),
|
|
||||||
actualDocument: &blobDoc,
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -345,7 +325,7 @@ func TestGetDocument(t *testing.T) {
|
||||||
func TestGetDocumentErrors(t *testing.T) {
|
func TestGetDocumentErrors(t *testing.T) {
|
||||||
dir.UserConfigDir = "/"
|
dir.UserConfigDir = "/"
|
||||||
t.Run("non-existing policy file", func(t *testing.T) {
|
t.Run("non-existing policy file", func(t *testing.T) {
|
||||||
var doc OCIDocument
|
var doc Document
|
||||||
if err := getDocument("blaah", &doc); err == nil || err.Error() != fmt.Sprintf("trust policy is not present. To create a trust policy, see: %s", trustPolicyLink) {
|
if err := getDocument("blaah", &doc); err == nil || err.Error() != fmt.Sprintf("trust policy is not present. To create a trust policy, see: %s", trustPolicyLink) {
|
||||||
t.Fatalf("getDocument() should throw error for non existent policy")
|
t.Fatalf("getDocument() should throw error for non existent policy")
|
||||||
}
|
}
|
||||||
|
@ -362,7 +342,7 @@ func TestGetDocumentErrors(t *testing.T) {
|
||||||
}
|
}
|
||||||
t.Cleanup(func() { os.RemoveAll(tempRoot) })
|
t.Cleanup(func() { os.RemoveAll(tempRoot) })
|
||||||
|
|
||||||
var doc OCIDocument
|
var doc Document
|
||||||
if err := getDocument(path, &doc); err == nil || err.Error() != fmt.Sprintf("malformed trust policy. To create a trust policy, see: %s", trustPolicyLink) {
|
if err := getDocument(path, &doc); err == nil || err.Error() != fmt.Sprintf("malformed trust policy. To create a trust policy, see: %s", trustPolicyLink) {
|
||||||
t.Fatalf("getDocument() should throw error for invalid policy file. Error: %v", err)
|
t.Fatalf("getDocument() should throw error for invalid policy file. Error: %v", err)
|
||||||
}
|
}
|
||||||
|
@ -379,7 +359,7 @@ func TestGetDocumentErrors(t *testing.T) {
|
||||||
t.Fatalf("creation of invalid permission policy file failed. Error: %v", err)
|
t.Fatalf("creation of invalid permission policy file failed. Error: %v", err)
|
||||||
}
|
}
|
||||||
expectedErrMsg := fmt.Sprintf("unable to read trust policy due to file permissions, please verify the permissions of %s", path)
|
expectedErrMsg := fmt.Sprintf("unable to read trust policy due to file permissions, please verify the permissions of %s", path)
|
||||||
var doc OCIDocument
|
var doc Document
|
||||||
if err := getDocument(path, &doc); err == nil || err.Error() != expectedErrMsg {
|
if err := getDocument(path, &doc); err == nil || err.Error() != expectedErrMsg {
|
||||||
t.Errorf("getDocument() should throw error for a policy file with bad permissions. "+
|
t.Errorf("getDocument() should throw error for a policy file with bad permissions. "+
|
||||||
"Expected error: '%v'qq but found '%v'", expectedErrMsg, err.Error())
|
"Expected error: '%v'qq but found '%v'", expectedErrMsg, err.Error())
|
||||||
|
@ -400,7 +380,7 @@ func TestGetDocumentErrors(t *testing.T) {
|
||||||
if err := os.Symlink(path, symlinkPath); err != nil {
|
if err := os.Symlink(path, symlinkPath); err != nil {
|
||||||
t.Fatalf("creation of symlink for policy file failed. Error: %v", err)
|
t.Fatalf("creation of symlink for policy file failed. Error: %v", err)
|
||||||
}
|
}
|
||||||
var doc OCIDocument
|
var doc Document
|
||||||
if err := getDocument(symlinkPath, &doc); err == nil || !strings.HasPrefix(err.Error(), "trust policy is not a regular file (symlinks are not supported)") {
|
if err := getDocument(symlinkPath, &doc); err == nil || !strings.HasPrefix(err.Error(), "trust policy is not a regular file (symlinks are not supported)") {
|
||||||
t.Fatalf("getDocument() should throw error for a symlink policy file. Error: %v", err)
|
t.Fatalf("getDocument() should throw error for a symlink policy file. Error: %v", err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,7 +15,6 @@
|
||||||
package truststore
|
package truststore
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"context"
|
"context"
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"errors"
|
"errors"
|
||||||
|
@ -30,7 +29,8 @@ import (
|
||||||
"github.com/notaryproject/notation-go/internal/slices"
|
"github.com/notaryproject/notation-go/internal/slices"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Type is an enum for trust store types supported
|
// Type is an enum for trust store types supported such as
|
||||||
|
// "ca" and "signingAuthority"
|
||||||
type Type string
|
type Type string
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -47,18 +47,18 @@ var (
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
// X509TrustStore provides list and get behaviors for the trust store
|
// X509TrustStore provide list and get behaviors for the trust store
|
||||||
type X509TrustStore interface {
|
type X509TrustStore interface {
|
||||||
// GetCertificates returns certificates under storeType/namedStore
|
// GetCertificates returns certificates under storeType/namedStore
|
||||||
GetCertificates(ctx context.Context, storeType Type, namedStore string) ([]*x509.Certificate, error)
|
GetCertificates(ctx context.Context, storeType Type, namedStore string) ([]*x509.Certificate, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewX509TrustStore generates a new [X509TrustStore]
|
// NewX509TrustStore generates a new X509TrustStore
|
||||||
func NewX509TrustStore(trustStorefs dir.SysFS) X509TrustStore {
|
func NewX509TrustStore(trustStorefs dir.SysFS) X509TrustStore {
|
||||||
return &x509TrustStore{trustStorefs}
|
return &x509TrustStore{trustStorefs}
|
||||||
}
|
}
|
||||||
|
|
||||||
// x509TrustStore implements [X509TrustStore]
|
// x509TrustStore implements X509TrustStore
|
||||||
type x509TrustStore struct {
|
type x509TrustStore struct {
|
||||||
trustStorefs dir.SysFS
|
trustStorefs dir.SysFS
|
||||||
}
|
}
|
||||||
|
@ -106,14 +106,6 @@ func (trustStore *x509TrustStore) GetCertificates(ctx context.Context, storeType
|
||||||
if err := ValidateCertificates(certs); err != nil {
|
if err := ValidateCertificates(certs); err != nil {
|
||||||
return nil, CertificateError{InnerError: err, Msg: fmt.Sprintf("failed to validate the trusted certificate %s in trust store %s of type %s", certFileName, namedStore, storeType)}
|
return nil, CertificateError{InnerError: err, Msg: fmt.Sprintf("failed to validate the trusted certificate %s in trust store %s of type %s", certFileName, namedStore, storeType)}
|
||||||
}
|
}
|
||||||
// we require TSA certificates in trust store to be root CA certificates
|
|
||||||
if storeType == TypeTSA {
|
|
||||||
for _, cert := range certs {
|
|
||||||
if err := isRootCACertificate(cert); err != nil {
|
|
||||||
return nil, CertificateError{InnerError: err, Msg: fmt.Sprintf("trusted certificate %s in trust store %s of type %s is invalid: %v", certFileName, namedStore, storeType, err.Error())}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
certificates = append(certificates, certs...)
|
certificates = append(certificates, certs...)
|
||||||
}
|
}
|
||||||
if len(certificates) < 1 {
|
if len(certificates) < 1 {
|
||||||
|
@ -145,14 +137,3 @@ func ValidateCertificates(certs []*x509.Certificate) error {
|
||||||
func isValidStoreType(storeType Type) bool {
|
func isValidStoreType(storeType Type) bool {
|
||||||
return slices.Contains(Types, storeType)
|
return slices.Contains(Types, storeType)
|
||||||
}
|
}
|
||||||
|
|
||||||
// isRootCACertificate returns nil if cert is a root CA certificate
|
|
||||||
func isRootCACertificate(cert *x509.Certificate) error {
|
|
||||||
if err := cert.CheckSignatureFrom(cert); err != nil {
|
|
||||||
return fmt.Errorf("certificate with subject %q is not a root CA certificate: %w", cert.Subject, err)
|
|
||||||
}
|
|
||||||
if !bytes.Equal(cert.RawSubject, cert.RawIssuer) {
|
|
||||||
return fmt.Errorf("certificate with subject %q is not a root CA certificate: issuer (%s) and subject (%s) are not the same", cert.Subject, cert.Issuer, cert.Subject)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
|
@ -98,31 +98,3 @@ func TestValidateCertsWithLeafCert(t *testing.T) {
|
||||||
t.Fatalf("leaf cert in a trust store should return error %q, got: %v", expectedErr, err)
|
t.Fatalf("leaf cert in a trust store should return error %q, got: %v", expectedErr, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGetCertFromValidTsaTrustStore(t *testing.T) {
|
|
||||||
// testing ../testdata/truststore/x509/tsa/test-nonCA/globalsignRoot.cer
|
|
||||||
_, err := trustStore.GetCertificates(context.Background(), "tsa", "test-timestamp")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("expected nil error, but got %s", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGetCertFromInvalidTsaTrustStore(t *testing.T) {
|
|
||||||
t.Run("non CA certificate", func(t *testing.T) {
|
|
||||||
// testing ../testdata/truststore/x509/tsa/test-nonCA/wabbit-networks.io
|
|
||||||
expectedErrMsg := `trusted certificate wabbit-networks.io.crt in trust store test-nonCA of type tsa is invalid: certificate with subject "CN=wabbit-networks.io,O=Notary,L=Seattle,ST=WA,C=US" is not a root CA certificate: x509: invalid signature: parent certificate cannot sign this kind of certificate`
|
|
||||||
_, err := trustStore.GetCertificates(context.Background(), "tsa", "test-nonCA")
|
|
||||||
if err == nil || err.Error() != expectedErrMsg {
|
|
||||||
t.Fatalf("expected error: %s, but got %s", expectedErrMsg, err)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("not self-issued", func(t *testing.T) {
|
|
||||||
//testing ../testdata/truststore/x509/tsa/test-nonSelfIssued/nonSelfIssued.crt
|
|
||||||
expectedErrMsg := `trusted certificate nonSelfIssued.crt in trust store test-nonSelfIssued of type tsa is invalid: certificate with subject "CN=Notation Test Revokable RSA Chain Cert 2,O=Notary,L=Seattle,ST=WA,C=US" is not a root CA certificate: issuer (CN=Notation Test Revokable RSA Chain Cert Root,O=Notary,L=Seattle,ST=WA,C=US) and subject (CN=Notation Test Revokable RSA Chain Cert 2,O=Notary,L=Seattle,ST=WA,C=US) are not the same`
|
|
||||||
_, err := trustStore.GetCertificates(context.Background(), "tsa", "test-nonSelfIssued")
|
|
||||||
if err == nil || err.Error() != expectedErrMsg {
|
|
||||||
t.Fatalf("expected error: %s, but got %s", expectedErrMsg, err)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
|
@ -11,13 +11,11 @@
|
||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
// Package verifier provides implementations of [notation.Verifier] and
|
// Package verifier provides an implementation of notation.Verifier interface
|
||||||
// [notation.BlobVerifier] interfaces.
|
|
||||||
package verifier
|
package verifier
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"crypto"
|
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
|
@ -48,21 +46,12 @@ import (
|
||||||
"github.com/notaryproject/notation-go/verifier/truststore"
|
"github.com/notaryproject/notation-go/verifier/truststore"
|
||||||
pluginframework "github.com/notaryproject/notation-plugin-framework-go/plugin"
|
pluginframework "github.com/notaryproject/notation-plugin-framework-go/plugin"
|
||||||
"github.com/notaryproject/tspclient-go"
|
"github.com/notaryproject/tspclient-go"
|
||||||
"github.com/opencontainers/go-digest"
|
|
||||||
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||||
)
|
)
|
||||||
|
|
||||||
var algorithms = map[crypto.Hash]digest.Algorithm{
|
// verifier implements notation.Verifier and notation.verifySkipper
|
||||||
crypto.SHA256: digest.SHA256,
|
|
||||||
crypto.SHA384: digest.SHA384,
|
|
||||||
crypto.SHA512: digest.SHA512,
|
|
||||||
}
|
|
||||||
|
|
||||||
// verifier implements [notation.Verifier], [notation.BlobVerifier] and
|
|
||||||
// notation.verifySkipper interfaces.
|
|
||||||
type verifier struct {
|
type verifier struct {
|
||||||
ociTrustPolicyDoc *trustpolicy.OCIDocument
|
trustPolicyDoc *trustpolicy.Document
|
||||||
blobTrustPolicyDoc *trustpolicy.BlobDocument
|
|
||||||
trustStore truststore.X509TrustStore
|
trustStore truststore.X509TrustStore
|
||||||
pluginManager plugin.Manager
|
pluginManager plugin.Manager
|
||||||
revocationClient revocation.Revocation
|
revocationClient revocation.Revocation
|
||||||
|
@ -71,7 +60,7 @@ type verifier struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// VerifierOptions specifies additional parameters that can be set when using
|
// VerifierOptions specifies additional parameters that can be set when using
|
||||||
// the [NewVerifierWithOptions] constructor
|
// the NewWithOptions constructor
|
||||||
type VerifierOptions struct {
|
type VerifierOptions struct {
|
||||||
// RevocationClient is an implementation of revocation.Revocation to use for
|
// RevocationClient is an implementation of revocation.Revocation to use for
|
||||||
// verifying revocation of code signing certificate chain
|
// verifying revocation of code signing certificate chain
|
||||||
|
@ -88,86 +77,44 @@ type VerifierOptions struct {
|
||||||
// RevocationTimestampingValidator is used for verifying revocation of
|
// RevocationTimestampingValidator is used for verifying revocation of
|
||||||
// timestamping certificate chain with context.
|
// timestamping certificate chain with context.
|
||||||
RevocationTimestampingValidator revocation.Validator
|
RevocationTimestampingValidator revocation.Validator
|
||||||
|
|
||||||
// OCITrustpolicy is the trust policy document for OCI artifacts.
|
|
||||||
OCITrustPolicy *trustpolicy.OCIDocument
|
|
||||||
|
|
||||||
// BlobTrustPolicy is the trust policy document for Blob artifacts.
|
|
||||||
BlobTrustPolicy *trustpolicy.BlobDocument
|
|
||||||
|
|
||||||
// PluginManager manages plugins installed on the system.
|
|
||||||
PluginManager plugin.Manager
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewOCIVerifierFromConfig returns an OCI verifier based on local file system
|
// NewFromConfig returns a verifier based on local file system.
|
||||||
func NewOCIVerifierFromConfig() (*verifier, error) {
|
func NewFromConfig() (notation.Verifier, error) {
|
||||||
// load trust policy
|
// load trust policy
|
||||||
policyDocument, err := trustpolicy.LoadOCIDocument()
|
policyDocument, err := trustpolicy.LoadDocument()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
// load trust store
|
// load trust store
|
||||||
x509TrustStore := truststore.NewX509TrustStore(dir.ConfigFS())
|
x509TrustStore := truststore.NewX509TrustStore(dir.ConfigFS())
|
||||||
|
|
||||||
return NewVerifierWithOptions(x509TrustStore, VerifierOptions{
|
return New(policyDocument, x509TrustStore, plugin.NewCLIManager(dir.PluginFS()))
|
||||||
OCITrustPolicy: policyDocument,
|
|
||||||
PluginManager: plugin.NewCLIManager(dir.PluginFS()),
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewBlobVerifierFromConfig returns a Blob verifier based on local file system
|
// New creates a new verifier given trustPolicy, trustStore and pluginManager
|
||||||
func NewBlobVerifierFromConfig() (*verifier, error) {
|
func New(trustPolicy *trustpolicy.Document, trustStore truststore.X509TrustStore, pluginManager plugin.Manager) (notation.Verifier, error) {
|
||||||
// load blob trust policy
|
return NewWithOptions(trustPolicy, trustStore, pluginManager, VerifierOptions{})
|
||||||
policyDocument, err := trustpolicy.LoadBlobDocument()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
// load trust store
|
|
||||||
x509TrustStore := truststore.NewX509TrustStore(dir.ConfigFS())
|
|
||||||
|
|
||||||
return NewVerifierWithOptions(x509TrustStore, VerifierOptions{
|
|
||||||
BlobTrustPolicy: policyDocument,
|
|
||||||
PluginManager: plugin.NewCLIManager(dir.PluginFS()),
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewWithOptions creates a new verifier given ociTrustPolicy, trustStore,
|
// NewWithOptions creates a new verifier given trustPolicy, trustStore,
|
||||||
// pluginManager, and VerifierOptions.
|
// pluginManager, and verifierOptions
|
||||||
//
|
func NewWithOptions(trustPolicy *trustpolicy.Document, trustStore truststore.X509TrustStore, pluginManager plugin.Manager, verifierOptions VerifierOptions) (notation.Verifier, error) {
|
||||||
// Deprecated: NewWithOptions function exists for historical compatibility and
|
|
||||||
// should not be used. To create verifier, use [NewVerifierWithOptions] function.
|
|
||||||
func NewWithOptions(ociTrustPolicy *trustpolicy.OCIDocument, trustStore truststore.X509TrustStore, pluginManager plugin.Manager, opts VerifierOptions) (notation.Verifier, error) {
|
|
||||||
opts.OCITrustPolicy = ociTrustPolicy
|
|
||||||
opts.PluginManager = pluginManager
|
|
||||||
return NewVerifierWithOptions(trustStore, opts)
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewVerifierWithOptions creates a new verifier given trustStore and
|
|
||||||
// verifierOptions.
|
|
||||||
func NewVerifierWithOptions(trustStore truststore.X509TrustStore, verifierOptions VerifierOptions) (*verifier, error) {
|
|
||||||
ociTrustPolicy := verifierOptions.OCITrustPolicy
|
|
||||||
blobTrustPolicy := verifierOptions.BlobTrustPolicy
|
|
||||||
if trustStore == nil {
|
if trustStore == nil {
|
||||||
return nil, errors.New("trustStore cannot be nil")
|
return nil, errors.New("trustStore cannot be nil")
|
||||||
}
|
}
|
||||||
if ociTrustPolicy == nil && blobTrustPolicy == nil {
|
|
||||||
return nil, errors.New("ociTrustPolicy and blobTrustPolicy both cannot be nil")
|
if trustPolicy == nil {
|
||||||
|
return nil, errors.New("trustPolicy cannot be nil")
|
||||||
}
|
}
|
||||||
if ociTrustPolicy != nil {
|
|
||||||
if err := ociTrustPolicy.Validate(); err != nil {
|
if err := trustPolicy.Validate(); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
|
||||||
}
|
|
||||||
if blobTrustPolicy != nil {
|
|
||||||
if err := blobTrustPolicy.Validate(); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
v := &verifier{
|
v := &verifier{
|
||||||
ociTrustPolicyDoc: ociTrustPolicy,
|
trustPolicyDoc: trustPolicy,
|
||||||
blobTrustPolicyDoc: blobTrustPolicy,
|
trustStore: trustStore,
|
||||||
trustStore: trustStore,
|
pluginManager: pluginManager,
|
||||||
pluginManager: verifierOptions.PluginManager,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := v.setRevocation(verifierOptions); err != nil {
|
if err := v.setRevocation(verifierOptions); err != nil {
|
||||||
|
@ -176,26 +123,6 @@ func NewVerifierWithOptions(trustStore truststore.X509TrustStore, verifierOption
|
||||||
return v, nil
|
return v, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewFromConfig returns an OCI verifier based on local file system.
|
|
||||||
//
|
|
||||||
// Deprecated: NewFromConfig function exists for historical compatibility and
|
|
||||||
// should not be used. To create an OCI verifier, use [NewOCIVerifierFromConfig]
|
|
||||||
// function.
|
|
||||||
func NewFromConfig() (notation.Verifier, error) {
|
|
||||||
return NewOCIVerifierFromConfig()
|
|
||||||
}
|
|
||||||
|
|
||||||
// New creates a new verifier given ociTrustPolicy, trustStore and pluginManager.
|
|
||||||
//
|
|
||||||
// Deprecated: New function exists for historical compatibility and
|
|
||||||
// should not be used. To create verifier, use [NewVerifier] function.
|
|
||||||
func New(ociTrustPolicy *trustpolicy.OCIDocument, trustStore truststore.X509TrustStore, pluginManager plugin.Manager) (notation.Verifier, error) {
|
|
||||||
return NewVerifierWithOptions(trustStore, VerifierOptions{
|
|
||||||
OCITrustPolicy: ociTrustPolicy,
|
|
||||||
PluginManager: pluginManager,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// setRevocation sets revocation validators of v
|
// setRevocation sets revocation validators of v
|
||||||
func (v *verifier) setRevocation(verifierOptions VerifierOptions) error {
|
func (v *verifier) setRevocation(verifierOptions VerifierOptions) error {
|
||||||
// timestamping validator
|
// timestamping validator
|
||||||
|
@ -241,7 +168,7 @@ func (v *verifier) SkipVerify(ctx context.Context, opts notation.VerifierVerifyO
|
||||||
logger := log.GetLogger(ctx)
|
logger := log.GetLogger(ctx)
|
||||||
|
|
||||||
logger.Debugf("Check verification level against artifact %v", opts.ArtifactReference)
|
logger.Debugf("Check verification level against artifact %v", opts.ArtifactReference)
|
||||||
trustPolicy, err := v.ociTrustPolicyDoc.GetApplicableTrustPolicy(opts.ArtifactReference)
|
trustPolicy, err := v.trustPolicyDoc.GetApplicableTrustPolicy(opts.ArtifactReference)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, nil, notation.ErrorNoApplicableTrustPolicy{Msg: err.Error()}
|
return false, nil, notation.ErrorNoApplicableTrustPolicy{Msg: err.Error()}
|
||||||
}
|
}
|
||||||
|
@ -258,88 +185,7 @@ func (v *verifier) SkipVerify(ctx context.Context, opts notation.VerifierVerifyO
|
||||||
return false, verificationLevel, nil
|
return false, verificationLevel, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// VerifyBlob verifies the signature of given blob, and returns the outcome upon
|
// Verify verifies the signature associated the target OCI
|
||||||
// successful verification.
|
|
||||||
func (v *verifier) VerifyBlob(ctx context.Context, descGenFunc notation.BlobDescriptorGenerator, signature []byte, opts notation.BlobVerifierVerifyOptions) (*notation.VerificationOutcome, error) {
|
|
||||||
logger := log.GetLogger(ctx)
|
|
||||||
logger.Debugf("Verify signature of media type %v", opts.SignatureMediaType)
|
|
||||||
if v.blobTrustPolicyDoc == nil {
|
|
||||||
return nil, errors.New("blobTrustPolicyDoc is nil")
|
|
||||||
}
|
|
||||||
|
|
||||||
var trustPolicy *trustpolicy.BlobTrustPolicy
|
|
||||||
var err error
|
|
||||||
if opts.TrustPolicyName == "" {
|
|
||||||
trustPolicy, err = v.blobTrustPolicyDoc.GetGlobalTrustPolicy()
|
|
||||||
} else {
|
|
||||||
trustPolicy, err = v.blobTrustPolicyDoc.GetApplicableTrustPolicy(opts.TrustPolicyName)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return nil, notation.ErrorNoApplicableTrustPolicy{Msg: err.Error()}
|
|
||||||
}
|
|
||||||
logger.Infof("Trust policy configuration: %+v", trustPolicy)
|
|
||||||
|
|
||||||
// ignore the error since we already validated the policy document
|
|
||||||
verificationLevel, _ := trustPolicy.SignatureVerification.GetVerificationLevel()
|
|
||||||
outcome := ¬ation.VerificationOutcome{
|
|
||||||
RawSignature: signature,
|
|
||||||
VerificationLevel: verificationLevel,
|
|
||||||
}
|
|
||||||
// verificationLevel is skip
|
|
||||||
if reflect.DeepEqual(verificationLevel, trustpolicy.LevelSkip) {
|
|
||||||
logger.Debug("Skipping signature verification")
|
|
||||||
return outcome, nil
|
|
||||||
}
|
|
||||||
err = v.processSignature(ctx, signature, opts.SignatureMediaType, trustPolicy.Name, trustPolicy.TrustedIdentities, trustPolicy.TrustStores, trustPolicy.SignatureVerification, opts.PluginConfig, outcome)
|
|
||||||
if err != nil {
|
|
||||||
outcome.Error = err
|
|
||||||
return outcome, err
|
|
||||||
}
|
|
||||||
|
|
||||||
payload := &envelope.Payload{}
|
|
||||||
err = json.Unmarshal(outcome.EnvelopeContent.Payload.Content, payload)
|
|
||||||
if err != nil {
|
|
||||||
logger.Error("Failed to unmarshal the payload content in the signature blob to envelope.Payload")
|
|
||||||
outcome.Error = err
|
|
||||||
return outcome, err
|
|
||||||
}
|
|
||||||
|
|
||||||
cryptoHash := outcome.EnvelopeContent.SignerInfo.SignatureAlgorithm.Hash()
|
|
||||||
digestAlgo, ok := algorithms[cryptoHash]
|
|
||||||
if !ok {
|
|
||||||
logger.Error("Unsupported hashing algorithm: %v", cryptoHash)
|
|
||||||
err := fmt.Errorf("unsupported hashing algorithm: %v", cryptoHash)
|
|
||||||
outcome.Error = err
|
|
||||||
return outcome, err
|
|
||||||
}
|
|
||||||
|
|
||||||
desc, err := descGenFunc(digestAlgo)
|
|
||||||
if err != nil {
|
|
||||||
errMsg := fmt.Sprintf("failed to generate descriptor for given artifact. Error: %s", err)
|
|
||||||
logger.Error(errMsg)
|
|
||||||
descErr := errors.New(errMsg)
|
|
||||||
outcome.Error = descErr
|
|
||||||
return outcome, descErr
|
|
||||||
}
|
|
||||||
|
|
||||||
if desc.Digest != payload.TargetArtifact.Digest || desc.Size != payload.TargetArtifact.Size ||
|
|
||||||
(desc.MediaType != "" && desc.MediaType != payload.TargetArtifact.MediaType) {
|
|
||||||
logger.Infof("payload present in the signature: %+v", payload.TargetArtifact)
|
|
||||||
logger.Infof("payload derived from the blob: %+v", desc)
|
|
||||||
outcome.Error = errors.New("integrity check failed. signature does not match the given blob")
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(opts.UserMetadata) > 0 {
|
|
||||||
err := verifyUserMetadata(logger, payload, opts.UserMetadata)
|
|
||||||
if err != nil {
|
|
||||||
outcome.Error = err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return outcome, outcome.Error
|
|
||||||
}
|
|
||||||
|
|
||||||
// Verify verifies the signature associated to the target OCI
|
|
||||||
// artifact with manifest descriptor `desc`, and returns the outcome upon
|
// artifact with manifest descriptor `desc`, and returns the outcome upon
|
||||||
// successful verification.
|
// successful verification.
|
||||||
// If nil signature is present and the verification level is not 'skip',
|
// If nil signature is present and the verification level is not 'skip',
|
||||||
|
@ -351,11 +197,11 @@ func (v *verifier) Verify(ctx context.Context, desc ocispec.Descriptor, signatur
|
||||||
logger := log.GetLogger(ctx)
|
logger := log.GetLogger(ctx)
|
||||||
|
|
||||||
logger.Debugf("Verify signature against artifact %v referenced as %s in signature media type %v", desc.Digest, artifactRef, envelopeMediaType)
|
logger.Debugf("Verify signature against artifact %v referenced as %s in signature media type %v", desc.Digest, artifactRef, envelopeMediaType)
|
||||||
if v.ociTrustPolicyDoc == nil {
|
if v.trustPolicyDoc == nil {
|
||||||
return nil, errors.New("ociTrustPolicyDoc is nil")
|
return nil, errors.New("trustPolicyDoc is nil")
|
||||||
}
|
}
|
||||||
|
|
||||||
trustPolicy, err := v.ociTrustPolicyDoc.GetApplicableTrustPolicy(artifactRef)
|
trustPolicy, err := v.trustPolicyDoc.GetApplicableTrustPolicy(artifactRef)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, notation.ErrorNoApplicableTrustPolicy{Msg: err.Error()}
|
return nil, notation.ErrorNoApplicableTrustPolicy{Msg: err.Error()}
|
||||||
}
|
}
|
||||||
|
@ -427,7 +273,7 @@ func (v *verifier) processSignature(ctx context.Context, sigBlob []byte, envelop
|
||||||
|
|
||||||
var installedPlugin pluginframework.VerifyPlugin
|
var installedPlugin pluginframework.VerifyPlugin
|
||||||
if verificationPluginName != "" {
|
if verificationPluginName != "" {
|
||||||
logger.Debugf("Finding verification plugin %q", verificationPluginName)
|
logger.Debugf("Finding verification plugin %s", verificationPluginName)
|
||||||
verificationPluginMinVersion, err := getVerificationPluginMinVersion(&outcome.EnvelopeContent.SignerInfo)
|
verificationPluginMinVersion, err := getVerificationPluginMinVersion(&outcome.EnvelopeContent.SignerInfo)
|
||||||
if err != nil && err != errExtendedAttributeNotExist {
|
if err != nil && err != errExtendedAttributeNotExist {
|
||||||
return notation.ErrorVerificationInconclusive{Msg: fmt.Sprintf("error while getting plugin minimum version, error: %s", err)}
|
return notation.ErrorVerificationInconclusive{Msg: fmt.Sprintf("error while getting plugin minimum version, error: %s", err)}
|
||||||
|
@ -828,40 +674,20 @@ func revocationFinalResult(certResults []*revocationresult.CertRevocationResult,
|
||||||
revokedFound := false
|
revokedFound := false
|
||||||
var revokedCertSubject string
|
var revokedCertSubject string
|
||||||
for i := len(certResults) - 1; i >= 0; i-- {
|
for i := len(certResults) - 1; i >= 0; i-- {
|
||||||
cert := certChain[i]
|
if len(certResults[i].ServerResults) > 0 && certResults[i].ServerResults[0].Error != nil {
|
||||||
certResult := certResults[i]
|
logger.Debugf("Error for certificate #%d in chain with subject %v for server %q: %v", (i + 1), certChain[i].Subject.String(), certResults[i].ServerResults[0].Server, certResults[i].ServerResults[0].Error)
|
||||||
if certResult.RevocationMethod == revocationresult.RevocationMethodOCSPFallbackCRL {
|
|
||||||
// log the fallback warning
|
|
||||||
logger.Warnf("OCSP check failed with unknown error and fallback to CRL check for certificate #%d in chain with subject %q", (i + 1), cert.Subject)
|
|
||||||
}
|
|
||||||
for _, serverResult := range certResult.ServerResults {
|
|
||||||
if serverResult.Error != nil {
|
|
||||||
// log individual server errors
|
|
||||||
if certResult.RevocationMethod == revocationresult.RevocationMethodOCSPFallbackCRL && serverResult.RevocationMethod == revocationresult.RevocationMethodOCSP {
|
|
||||||
// when the final revocation method is OCSPFallbackCRL,
|
|
||||||
// the OCSP server results should not be logged as an error
|
|
||||||
// since the CRL revocation check can succeed.
|
|
||||||
logger.Debugf("Certificate #%d in chain with subject %q encountered an error for revocation method %s at URL %q: %v", (i + 1), cert.Subject, revocationresult.RevocationMethodOCSP, serverResult.Server, serverResult.Error)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
logger.Errorf("Certificate #%d in chain with subject %q encountered an error for revocation method %s at URL %q: %v", (i + 1), cert.Subject, serverResult.RevocationMethod, serverResult.Server, serverResult.Error)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if certResult.Result == revocationresult.ResultOK || certResult.Result == revocationresult.ResultNonRevokable {
|
if certResults[i].Result == revocationresult.ResultOK || certResults[i].Result == revocationresult.ResultNonRevokable {
|
||||||
numOKResults++
|
numOKResults++
|
||||||
} else {
|
} else {
|
||||||
finalResult = certResult.Result
|
finalResult = certResults[i].Result
|
||||||
problematicCertSubject = cert.Subject.String()
|
problematicCertSubject = certChain[i].Subject.String()
|
||||||
if certResult.Result == revocationresult.ResultRevoked {
|
if certResults[i].Result == revocationresult.ResultRevoked {
|
||||||
revokedFound = true
|
revokedFound = true
|
||||||
revokedCertSubject = problematicCertSubject
|
revokedCertSubject = problematicCertSubject
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if i < len(certResults)-1 && certResult.Result == revocationresult.ResultNonRevokable {
|
|
||||||
logger.Warnf("Certificate #%d in the chain with subject %q neither has an OCSP nor a CRL revocation method.", (i + 1), cert.Subject)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if revokedFound {
|
if revokedFound {
|
||||||
problematicCertSubject = revokedCertSubject
|
problematicCertSubject = revokedCertSubject
|
||||||
|
@ -1042,7 +868,7 @@ func verifyTimestamp(ctx context.Context, policyName string, trustStores []strin
|
||||||
}
|
}
|
||||||
|
|
||||||
// Performing timestamp verification
|
// Performing timestamp verification
|
||||||
logger.Debug("Performing timestamp verification...")
|
logger.Info("Performing timestamp verification...")
|
||||||
|
|
||||||
// 1. Timestamp countersignature MUST be present
|
// 1. Timestamp countersignature MUST be present
|
||||||
logger.Debug("Checking timestamp countersignature existence...")
|
logger.Debug("Checking timestamp countersignature existence...")
|
||||||
|
@ -1088,7 +914,7 @@ func verifyTimestamp(ctx context.Context, policyName string, trustStores []strin
|
||||||
if err := nx509.ValidateTimestampingCertChain(tsaCertChain); err != nil {
|
if err := nx509.ValidateTimestampingCertChain(tsaCertChain); err != nil {
|
||||||
return fmt.Errorf("failed to validate the timestamping certificate chain with error: %w", err)
|
return fmt.Errorf("failed to validate the timestamping certificate chain with error: %w", err)
|
||||||
}
|
}
|
||||||
logger.Debug("The subject of TSA signing certificate is: ", tsaCertChain[0].Subject)
|
logger.Info("TSA identity is: ", tsaCertChain[0].Subject)
|
||||||
|
|
||||||
// 4. Check the timestamp against the signing certificate chain
|
// 4. Check the timestamp against the signing certificate chain
|
||||||
logger.Debug("Checking the timestamp against the signing certificate chain...")
|
logger.Debug("Checking the timestamp against the signing certificate chain...")
|
||||||
|
@ -1100,9 +926,6 @@ func verifyTimestamp(ctx context.Context, policyName string, trustStores []strin
|
||||||
if !timestamp.BoundedBefore(cert.NotAfter) {
|
if !timestamp.BoundedBefore(cert.NotAfter) {
|
||||||
return fmt.Errorf("timestamp can be after certificate %q validity period, it was expired at %q", cert.Subject, cert.NotAfter.Format(time.RFC1123Z))
|
return fmt.Errorf("timestamp can be after certificate %q validity period, it was expired at %q", cert.Subject, cert.NotAfter.Format(time.RFC1123Z))
|
||||||
}
|
}
|
||||||
if timeOfVerification.After(cert.NotAfter) {
|
|
||||||
logger.Debugf("Certificate %q expired at %q, but timestamp is within certificate validity period", cert.Subject, cert.NotAfter.Format(time.RFC1123Z))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 5. Perform the timestamping certificate chain revocation check
|
// 5. Perform the timestamping certificate chain revocation check
|
||||||
|
@ -1125,6 +948,5 @@ func verifyTimestamp(ctx context.Context, policyName string, trustStores []strin
|
||||||
}
|
}
|
||||||
|
|
||||||
// success
|
// success
|
||||||
logger.Debug("Timestamp verification: Success")
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,15 +16,12 @@ package verifier
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"crypto/x509/pkix"
|
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"encoding/pem"
|
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"reflect"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
@ -33,61 +30,28 @@ import (
|
||||||
|
|
||||||
"github.com/notaryproject/notation-core-go/revocation"
|
"github.com/notaryproject/notation-core-go/revocation"
|
||||||
"github.com/notaryproject/notation-core-go/revocation/purpose"
|
"github.com/notaryproject/notation-core-go/revocation/purpose"
|
||||||
"github.com/notaryproject/notation-core-go/revocation/result"
|
|
||||||
revocationresult "github.com/notaryproject/notation-core-go/revocation/result"
|
|
||||||
"github.com/notaryproject/notation-core-go/signature"
|
"github.com/notaryproject/notation-core-go/signature"
|
||||||
_ "github.com/notaryproject/notation-core-go/signature/cose"
|
_ "github.com/notaryproject/notation-core-go/signature/cose"
|
||||||
"github.com/notaryproject/notation-core-go/signature/jws"
|
|
||||||
"github.com/notaryproject/notation-core-go/testhelper"
|
"github.com/notaryproject/notation-core-go/testhelper"
|
||||||
corex509 "github.com/notaryproject/notation-core-go/x509"
|
corex509 "github.com/notaryproject/notation-core-go/x509"
|
||||||
"github.com/notaryproject/notation-go"
|
"github.com/notaryproject/notation-go"
|
||||||
"github.com/notaryproject/notation-go/dir"
|
"github.com/notaryproject/notation-go/dir"
|
||||||
"github.com/notaryproject/notation-go/internal/envelope"
|
"github.com/notaryproject/notation-go/internal/envelope"
|
||||||
"github.com/notaryproject/notation-go/internal/mock"
|
"github.com/notaryproject/notation-go/internal/mock"
|
||||||
"github.com/notaryproject/notation-go/log"
|
|
||||||
"github.com/notaryproject/notation-go/plugin/proto"
|
"github.com/notaryproject/notation-go/plugin/proto"
|
||||||
"github.com/notaryproject/notation-go/signer"
|
"github.com/notaryproject/notation-go/signer"
|
||||||
"github.com/notaryproject/notation-go/verifier/trustpolicy"
|
"github.com/notaryproject/notation-go/verifier/trustpolicy"
|
||||||
"github.com/notaryproject/notation-go/verifier/truststore"
|
"github.com/notaryproject/notation-go/verifier/truststore"
|
||||||
"github.com/opencontainers/go-digest"
|
|
||||||
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||||
)
|
)
|
||||||
|
|
||||||
var testSig = `{"payload":"eyJ0YXJnZXRBcnRpZmFjdCI6eyJhbm5vdGF0aW9ucyI6eyJidWlsZElkIjoiMTAxIn0sImRpZ2VzdCI6InNoYTM4NDpiOGFiMjRkYWZiYTVjZjdlNGM4OWM1NjJmODExY2YxMDQ5M2Q0MjAzZGE5ODJkM2IxMzQ1ZjM2NmNhODYzZDljMmVkMzIzZGJkMGZiN2ZmODNhODAzMDJjZWZmYTVhNjEiLCJtZWRpYVR5cGUiOiJ2aWRlby9tcDQiLCJzaXplIjoxMn19","protected":"eyJhbGciOiJQUzM4NCIsImNyaXQiOlsiaW8uY25jZi5ub3Rhcnkuc2lnbmluZ1NjaGVtZSJdLCJjdHkiOiJhcHBsaWNhdGlvbi92bmQuY25jZi5ub3RhcnkucGF5bG9hZC52MStqc29uIiwiaW8uY25jZi5ub3Rhcnkuc2lnbmluZ1NjaGVtZSI6Im5vdGFyeS54NTA5IiwiaW8uY25jZi5ub3Rhcnkuc2lnbmluZ1RpbWUiOiIyMDI0LTA0LTA0VDE1OjAzOjA2LTA3OjAwIn0","header":{"x5c":["MIIEbTCCAtWgAwIBAgICAK0wDQYJKoZIhvcNAQELBQAwZDELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAldBMRAwDgYDVQQHEwdTZWF0dGxlMQ8wDQYDVQQKEwZOb3RhcnkxJTAjBgNVBAMTHE5vdGF0aW9uIEV4YW1wbGUgc2VsZi1zaWduZWQwIBcNMjQwNDA0MjIwMzA1WhgPMjEyNDA0MDQyMjAzMDVaMGQxCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJXQTEQMA4GA1UEBxMHU2VhdHRsZTEPMA0GA1UEChMGTm90YXJ5MSUwIwYDVQQDExxOb3RhdGlvbiBFeGFtcGxlIHNlbGYtc2lnbmVkMIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEA0dXD9UqzZcGlBlvPHO2uf+Sel/xwf/eOMS6Q30GV6JPeu9czLmyR0YMfC6P0N4zDzVYYZtQLkS5lalTMGX9A3yj9aXtXvtoYtLx2mF1CfdQJMcrT63wVVTWiPPe2JT8KHkkiACzVY6LTwc4s+DIAw9Gv21Uu6bFy4WWlGMp8UwTucR0JqaFoXzB6vxVRTkK8RRLM9Pj0hM5NwobpuZ+pc+ZS/7PhdvQHVzHeLLV9S7fHxw3n1c0ti8VUjSPSqCIEqOL3Eu/0pWMXB2A1xzn3RBfnzZMD3Tw3ksFgLMVzblhv41c6gr4cgjaS4wWwUvq9Xndd7Io8QNvxyiRDX5cHwQSEOmDfmegTIaLR0dKfvjY4ZJq8Y1DnaXU4RD6XeihtZykMlx7nTUyZZXpQ1akjh3VMzPykJ4mIknHh02zGRT9ZE8E1kYzRWhU/0MAzVrTTFHpric6jO459ouTnQXFjKwAcoD5+bNY6TuhC18iar7+l4BPPI1mFuqETnMfkkJQZAgMBAAGjJzAlMA4GA1UdDwEB/wQEAwIHgDATBgNVHSUEDDAKBggrBgEFBQcDAzANBgkqhkiG9w0BAQsFAAOCAYEAe5wyQPo+h1Yk2PkaA5aJKuU8azF2pTLfhQwAn/1XqPcmhNQuomOP0waoBsh+6sexfIDZaNuJ+zZUxqYHke/23+768SMiJCpuJfion3ak3Ka/IVNz48G0V+V+Vog+elkZzpdUQd30njLVcoQsihp0I/Gs3pnG2SeHmsdvYVuzycdYWTt5BFu4N8VWg4x4pfRMgDG7HGxRAacz2vTdqAx6rpWjO4xc0ZO8iUKjAeKHc7RuSx2dhUaRP9P8G8NBNtG6xNnbXIEjH6kP05srFRZ2jxm1an7sjsOpbBdIDztc0J+cb5yjBx7zo1OzWcmDUqMEXDR/WoygPzwhhHvWWvTqwVSEUvYnSaI6wxyHGxPFuX3+vCEZxU8NEGIuJtfYXWeo9cev5+PqjDgVu0uCWF53ZFsXNWbpff1qpG/CgrpFh3vN6uquMK9H5zaJBKr0GZFUsNRB1S8cUBgcjIZlWv3wrJQaOIFzF4RFO9dsYcG/b7ubdqSNGe4qfbsyuWf+1xsx"],"io.cncf.notary.signingAgent":"example signing agent"},"signature":"WMtF0u9GnQxJCpgrcxKZtNKNf3fvu2vnvOjd_2vQvjB4I9YKRYDQdr1q0AC0rU9b5aAGqP6Uh3jTbPkHHmOzGhXhRtidunfzOAeC6dPinR_RlnVMnVUY4cimZZG6Tg2tlgqGazgdzphnuZQpxUnK5mSInnWztXz_1-l_UJdPII49loJVE23hvWKDp8xOvMLftFXFlCYF9wE1ecTsYEAdrgB_XurFqbhhfeNcYie02aSMXfN0-ip9MHlIPhGrrOKLVm0w_S3nNBnuHHZ5lARgTm7tHtiNC0XxGCCk8qqteRZ4Vm2VM_UFMVOpdfh5KE_iTzmPCiHfNOJfgmvg5nysL1XUwGJ_KzCkPfY1Hq_4k73lia6RS6NSl1bSQ_s3uMBm3nx74WCmjK89RAihMIQ6s0PmUKQoWsIZ_5lWZ6uFW6LreoYyBFwvVVsSGSUx54-Gh76bwrt75va2VHpolSEXdhjcTK0KgscKLjU-LYDA_JD6AUaCi3WzMnpMSnO-9u_G"}`
|
var policy = dummyPolicyDocument()
|
||||||
var trustedCert = `-----BEGIN CERTIFICATE-----
|
var invalidPolicy = dummyInvalidPolicyDocument()
|
||||||
MIIEbTCCAtWgAwIBAgICAK0wDQYJKoZIhvcNAQELBQAwZDELMAkGA1UEBhMCVVMx
|
|
||||||
CzAJBgNVBAgTAldBMRAwDgYDVQQHEwdTZWF0dGxlMQ8wDQYDVQQKEwZOb3Rhcnkx
|
|
||||||
JTAjBgNVBAMTHE5vdGF0aW9uIEV4YW1wbGUgc2VsZi1zaWduZWQwIBcNMjQwNDA0
|
|
||||||
MjIwMzA1WhgPMjEyNDA0MDQyMjAzMDVaMGQxCzAJBgNVBAYTAlVTMQswCQYDVQQI
|
|
||||||
EwJXQTEQMA4GA1UEBxMHU2VhdHRsZTEPMA0GA1UEChMGTm90YXJ5MSUwIwYDVQQD
|
|
||||||
ExxOb3RhdGlvbiBFeGFtcGxlIHNlbGYtc2lnbmVkMIIBojANBgkqhkiG9w0BAQEF
|
|
||||||
AAOCAY8AMIIBigKCAYEA0dXD9UqzZcGlBlvPHO2uf+Sel/xwf/eOMS6Q30GV6JPe
|
|
||||||
u9czLmyR0YMfC6P0N4zDzVYYZtQLkS5lalTMGX9A3yj9aXtXvtoYtLx2mF1CfdQJ
|
|
||||||
McrT63wVVTWiPPe2JT8KHkkiACzVY6LTwc4s+DIAw9Gv21Uu6bFy4WWlGMp8UwTu
|
|
||||||
cR0JqaFoXzB6vxVRTkK8RRLM9Pj0hM5NwobpuZ+pc+ZS/7PhdvQHVzHeLLV9S7fH
|
|
||||||
xw3n1c0ti8VUjSPSqCIEqOL3Eu/0pWMXB2A1xzn3RBfnzZMD3Tw3ksFgLMVzblhv
|
|
||||||
41c6gr4cgjaS4wWwUvq9Xndd7Io8QNvxyiRDX5cHwQSEOmDfmegTIaLR0dKfvjY4
|
|
||||||
ZJq8Y1DnaXU4RD6XeihtZykMlx7nTUyZZXpQ1akjh3VMzPykJ4mIknHh02zGRT9Z
|
|
||||||
E8E1kYzRWhU/0MAzVrTTFHpric6jO459ouTnQXFjKwAcoD5+bNY6TuhC18iar7+l
|
|
||||||
4BPPI1mFuqETnMfkkJQZAgMBAAGjJzAlMA4GA1UdDwEB/wQEAwIHgDATBgNVHSUE
|
|
||||||
DDAKBggrBgEFBQcDAzANBgkqhkiG9w0BAQsFAAOCAYEAe5wyQPo+h1Yk2PkaA5aJ
|
|
||||||
KuU8azF2pTLfhQwAn/1XqPcmhNQuomOP0waoBsh+6sexfIDZaNuJ+zZUxqYHke/2
|
|
||||||
3+768SMiJCpuJfion3ak3Ka/IVNz48G0V+V+Vog+elkZzpdUQd30njLVcoQsihp0
|
|
||||||
I/Gs3pnG2SeHmsdvYVuzycdYWTt5BFu4N8VWg4x4pfRMgDG7HGxRAacz2vTdqAx6
|
|
||||||
rpWjO4xc0ZO8iUKjAeKHc7RuSx2dhUaRP9P8G8NBNtG6xNnbXIEjH6kP05srFRZ2
|
|
||||||
jxm1an7sjsOpbBdIDztc0J+cb5yjBx7zo1OzWcmDUqMEXDR/WoygPzwhhHvWWvTq
|
|
||||||
wVSEUvYnSaI6wxyHGxPFuX3+vCEZxU8NEGIuJtfYXWeo9cev5+PqjDgVu0uCWF53
|
|
||||||
ZFsXNWbpff1qpG/CgrpFh3vN6uquMK9H5zaJBKr0GZFUsNRB1S8cUBgcjIZlWv3w
|
|
||||||
rJQaOIFzF4RFO9dsYcG/b7ubdqSNGe4qfbsyuWf+1xsx
|
|
||||||
-----END CERTIFICATE-----`
|
|
||||||
|
|
||||||
var ociPolicy = dummyOCIPolicyDocument()
|
|
||||||
var blobPolicy = dummyBlobPolicyDocument()
|
|
||||||
var store = truststore.NewX509TrustStore(dir.ConfigFS())
|
var store = truststore.NewX509TrustStore(dir.ConfigFS())
|
||||||
var pm = mock.PluginManager{}
|
var pm = mock.PluginManager{}
|
||||||
|
|
||||||
func TestNewVerifier_Error(t *testing.T) {
|
func TestNewVerifier_Error(t *testing.T) {
|
||||||
policyDocument := dummyOCIPolicyDocument()
|
policyDocument := dummyPolicyDocument()
|
||||||
_, err := New(&policyDocument, nil, nil)
|
_, err := New(&policyDocument, nil, nil)
|
||||||
expectedErr := errors.New("trustStore cannot be nil")
|
expectedErr := errors.New("trustStore cannot be nil")
|
||||||
if err == nil || err.Error() != expectedErr.Error() {
|
if err == nil || err.Error() != expectedErr.Error() {
|
||||||
|
@ -95,10 +59,31 @@ func TestNewVerifier_Error(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestNewFromConfig(t *testing.T) {
|
||||||
|
tempRoot := t.TempDir()
|
||||||
|
dir.UserConfigDir = tempRoot
|
||||||
|
expectedErrMsg := "trust policy is not present. To create a trust policy, see: https://notaryproject.dev/docs/quickstart/#create-a-trust-policy"
|
||||||
|
_, err := NewFromConfig()
|
||||||
|
if err == nil || err.Error() != expectedErrMsg {
|
||||||
|
t.Fatalf("expected %s, but got %s", expectedErrMsg, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
path := filepath.Join(tempRoot, "trustpolicy.json")
|
||||||
|
policyJson, _ := json.Marshal(dummyPolicyDocument())
|
||||||
|
if err := os.WriteFile(path, policyJson, 0600); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
t.Cleanup(func() { os.RemoveAll(tempRoot) })
|
||||||
|
_, err = NewFromConfig()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestInvalidArtifactUriValidations(t *testing.T) {
|
func TestInvalidArtifactUriValidations(t *testing.T) {
|
||||||
verifier := verifier{
|
verifier := verifier{
|
||||||
ociTrustPolicyDoc: &ociPolicy,
|
trustPolicyDoc: &policy,
|
||||||
pluginManager: mock.PluginManager{},
|
pluginManager: mock.PluginManager{},
|
||||||
}
|
}
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
|
@ -128,12 +113,12 @@ func TestInvalidArtifactUriValidations(t *testing.T) {
|
||||||
|
|
||||||
func TestErrorNoApplicableTrustPolicy_Error(t *testing.T) {
|
func TestErrorNoApplicableTrustPolicy_Error(t *testing.T) {
|
||||||
verifier := verifier{
|
verifier := verifier{
|
||||||
ociTrustPolicyDoc: &ociPolicy,
|
trustPolicyDoc: &policy,
|
||||||
pluginManager: mock.PluginManager{},
|
pluginManager: mock.PluginManager{},
|
||||||
}
|
}
|
||||||
opts := notation.VerifierVerifyOptions{ArtifactReference: "non-existent-domain.com/repo@sha256:73c803930ea3ba1e54bc25c2bdc53edd0284c62ed651fe7b00369da519a3c333"}
|
opts := notation.VerifierVerifyOptions{ArtifactReference: "non-existent-domain.com/repo@sha256:73c803930ea3ba1e54bc25c2bdc53edd0284c62ed651fe7b00369da519a3c333"}
|
||||||
_, err := verifier.Verify(context.Background(), ocispec.Descriptor{}, []byte{}, opts)
|
_, err := verifier.Verify(context.Background(), ocispec.Descriptor{}, []byte{}, opts)
|
||||||
if !errors.Is(err, notation.ErrorNoApplicableTrustPolicy{Msg: "artifact \"non-existent-domain.com/repo@sha256:73c803930ea3ba1e54bc25c2bdc53edd0284c62ed651fe7b00369da519a3c333\" has no applicable oci trust policy statement. Trust policy applicability for a given artifact is determined by registryScopes. To create a trust policy, see: https://notaryproject.dev/docs/quickstart/#create-a-trust-policy"}) {
|
if !errors.Is(err, notation.ErrorNoApplicableTrustPolicy{Msg: "artifact \"non-existent-domain.com/repo@sha256:73c803930ea3ba1e54bc25c2bdc53edd0284c62ed651fe7b00369da519a3c333\" has no applicable trust policy statement. Trust policy applicability for a given artifact is determined by registryScopes. To create a trust policy, see: https://notaryproject.dev/docs/quickstart/#create-a-trust-policy"}) {
|
||||||
t.Fatalf("no applicable trust policy must throw error")
|
t.Fatalf("no applicable trust policy must throw error")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -172,7 +157,7 @@ func assertNotationVerification(t *testing.T, scheme signature.SigningScheme) {
|
||||||
|
|
||||||
// Unsupported Signature Envelope
|
// Unsupported Signature Envelope
|
||||||
for _, level := range verificationLevels {
|
for _, level := range verificationLevels {
|
||||||
policyDocument := dummyOCIPolicyDocument()
|
policyDocument := dummyPolicyDocument()
|
||||||
expectedErr := fmt.Errorf("unable to parse the digital signature, error : signature envelope format with media type \"application/unsupported+json\" is not supported")
|
expectedErr := fmt.Errorf("unable to parse the digital signature, error : signature envelope format with media type \"application/unsupported+json\" is not supported")
|
||||||
testCases = append(testCases, testCase{
|
testCases = append(testCases, testCase{
|
||||||
verificationType: trustpolicy.TypeIntegrity,
|
verificationType: trustpolicy.TypeIntegrity,
|
||||||
|
@ -185,7 +170,7 @@ func assertNotationVerification(t *testing.T, scheme signature.SigningScheme) {
|
||||||
|
|
||||||
// Integrity Success
|
// Integrity Success
|
||||||
for _, level := range verificationLevels {
|
for _, level := range verificationLevels {
|
||||||
policyDocument := dummyOCIPolicyDocument()
|
policyDocument := dummyPolicyDocument()
|
||||||
testCases = append(testCases, testCase{
|
testCases = append(testCases, testCase{
|
||||||
signatureBlob: validSigEnv,
|
signatureBlob: validSigEnv,
|
||||||
verificationType: trustpolicy.TypeIntegrity,
|
verificationType: trustpolicy.TypeIntegrity,
|
||||||
|
@ -197,7 +182,7 @@ func assertNotationVerification(t *testing.T, scheme signature.SigningScheme) {
|
||||||
|
|
||||||
// Integrity Failure
|
// Integrity Failure
|
||||||
for _, level := range verificationLevels {
|
for _, level := range verificationLevels {
|
||||||
policyDocument := dummyOCIPolicyDocument()
|
policyDocument := dummyPolicyDocument()
|
||||||
expectedErr := fmt.Errorf("signature is invalid. Error: illegal base64 data at input byte 242")
|
expectedErr := fmt.Errorf("signature is invalid. Error: illegal base64 data at input byte 242")
|
||||||
testCases = append(testCases, testCase{
|
testCases = append(testCases, testCase{
|
||||||
signatureBlob: invalidSigEnv,
|
signatureBlob: invalidSigEnv,
|
||||||
|
@ -211,7 +196,7 @@ func assertNotationVerification(t *testing.T, scheme signature.SigningScheme) {
|
||||||
|
|
||||||
// Authenticity Success
|
// Authenticity Success
|
||||||
for _, level := range verificationLevels {
|
for _, level := range verificationLevels {
|
||||||
policyDocument := dummyOCIPolicyDocument() // trust store is configured with the root certificate of the signature by default
|
policyDocument := dummyPolicyDocument() // trust store is configured with the root certificate of the signature by default
|
||||||
testCases = append(testCases, testCase{
|
testCases = append(testCases, testCase{
|
||||||
signatureBlob: validSigEnv,
|
signatureBlob: validSigEnv,
|
||||||
verificationType: trustpolicy.TypeAuthenticity,
|
verificationType: trustpolicy.TypeAuthenticity,
|
||||||
|
@ -223,9 +208,9 @@ func assertNotationVerification(t *testing.T, scheme signature.SigningScheme) {
|
||||||
|
|
||||||
// Authenticity Failure
|
// Authenticity Failure
|
||||||
for _, level := range verificationLevels {
|
for _, level := range verificationLevels {
|
||||||
policyDocument := dummyOCIPolicyDocument()
|
policyDocument := dummyPolicyDocument()
|
||||||
policyDocument.TrustPolicies[0].TrustStores = []string{"ca:valid-trust-store-2", "signingAuthority:valid-trust-store-2"} // trust store is not configured with the root certificate of the signature
|
policyDocument.TrustPolicies[0].TrustStores = []string{"ca:valid-trust-store-2", "signingAuthority:valid-trust-store-2"} // trust store is not configured with the root certificate of the signature
|
||||||
expectedErr := fmt.Errorf("the signature's certificate chain does not contain any trusted certificate")
|
expectedErr := fmt.Errorf("signature is not produced by a trusted signer")
|
||||||
testCases = append(testCases, testCase{
|
testCases = append(testCases, testCase{
|
||||||
signatureBlob: validSigEnv,
|
signatureBlob: validSigEnv,
|
||||||
verificationType: trustpolicy.TypeAuthenticity,
|
verificationType: trustpolicy.TypeAuthenticity,
|
||||||
|
@ -238,7 +223,7 @@ func assertNotationVerification(t *testing.T, scheme signature.SigningScheme) {
|
||||||
|
|
||||||
// Authenticity Failure with trust store missing separator
|
// Authenticity Failure with trust store missing separator
|
||||||
for _, level := range verificationLevels {
|
for _, level := range verificationLevels {
|
||||||
policyDocument := dummyOCIPolicyDocument()
|
policyDocument := dummyPolicyDocument()
|
||||||
policyDocument.TrustPolicies[0].TrustStores = []string{"ca:valid-trust-store-2", "signingAuthority"}
|
policyDocument.TrustPolicies[0].TrustStores = []string{"ca:valid-trust-store-2", "signingAuthority"}
|
||||||
expectedErr := fmt.Errorf("error while loading the trust store, trust policy statement \"test-statement-name\" is missing separator in trust store value \"signingAuthority\". The required format is <TrustStoreType>:<TrustStoreName>")
|
expectedErr := fmt.Errorf("error while loading the trust store, trust policy statement \"test-statement-name\" is missing separator in trust store value \"signingAuthority\". The required format is <TrustStoreType>:<TrustStoreName>")
|
||||||
testCases = append(testCases, testCase{
|
testCases = append(testCases, testCase{
|
||||||
|
@ -253,7 +238,7 @@ func assertNotationVerification(t *testing.T, scheme signature.SigningScheme) {
|
||||||
|
|
||||||
// TrustedIdentity Failure
|
// TrustedIdentity Failure
|
||||||
for _, level := range verificationLevels {
|
for _, level := range verificationLevels {
|
||||||
policyDocument := dummyOCIPolicyDocument()
|
policyDocument := dummyPolicyDocument()
|
||||||
policyDocument.TrustPolicies[0].TrustedIdentities = []string{"x509.subject:CN=LOL,O=DummyOrg,L=Hyderabad,ST=TG,C=IN"} // configure policy to not trust "CN=Notation Test Leaf Cert,O=Notary,L=Seattle,ST=WA,C=US" which is the subject of the signature's signing certificate
|
policyDocument.TrustPolicies[0].TrustedIdentities = []string{"x509.subject:CN=LOL,O=DummyOrg,L=Hyderabad,ST=TG,C=IN"} // configure policy to not trust "CN=Notation Test Leaf Cert,O=Notary,L=Seattle,ST=WA,C=US" which is the subject of the signature's signing certificate
|
||||||
expectedErr := fmt.Errorf("signing certificate from the digital signature does not match the X.509 trusted identities [map[\"C\":\"IN\" \"CN\":\"LOL\" \"L\":\"Hyderabad\" \"O\":\"DummyOrg\" \"ST\":\"TG\"]] defined in the trust policy \"test-statement-name\"")
|
expectedErr := fmt.Errorf("signing certificate from the digital signature does not match the X.509 trusted identities [map[\"C\":\"IN\" \"CN\":\"LOL\" \"L\":\"Hyderabad\" \"O\":\"DummyOrg\" \"ST\":\"TG\"]] defined in the trust policy \"test-statement-name\"")
|
||||||
testCases = append(testCases, testCase{
|
testCases = append(testCases, testCase{
|
||||||
|
@ -268,7 +253,7 @@ func assertNotationVerification(t *testing.T, scheme signature.SigningScheme) {
|
||||||
|
|
||||||
// TrustedIdentity Failure without separator
|
// TrustedIdentity Failure without separator
|
||||||
for _, level := range verificationLevels {
|
for _, level := range verificationLevels {
|
||||||
policyDocument := dummyOCIPolicyDocument()
|
policyDocument := dummyPolicyDocument()
|
||||||
policyDocument.TrustPolicies[0].TrustedIdentities = []string{"x509.subject"}
|
policyDocument.TrustPolicies[0].TrustedIdentities = []string{"x509.subject"}
|
||||||
expectedErr := fmt.Errorf("trust policy statement \"test-statement-name\" has trusted identity \"x509.subject\" missing separator")
|
expectedErr := fmt.Errorf("trust policy statement \"test-statement-name\" has trusted identity \"x509.subject\" missing separator")
|
||||||
testCases = append(testCases, testCase{
|
testCases = append(testCases, testCase{
|
||||||
|
@ -283,7 +268,7 @@ func assertNotationVerification(t *testing.T, scheme signature.SigningScheme) {
|
||||||
|
|
||||||
// TrustedIdentity Failure with empty value
|
// TrustedIdentity Failure with empty value
|
||||||
for _, level := range verificationLevels {
|
for _, level := range verificationLevels {
|
||||||
policyDocument := dummyOCIPolicyDocument()
|
policyDocument := dummyPolicyDocument()
|
||||||
policyDocument.TrustPolicies[0].TrustedIdentities = []string{"x509.subject:"}
|
policyDocument.TrustPolicies[0].TrustedIdentities = []string{"x509.subject:"}
|
||||||
expectedErr := fmt.Errorf("trust policy statement \"test-statement-name\" has trusted identity \"x509.subject:\" without an identity value")
|
expectedErr := fmt.Errorf("trust policy statement \"test-statement-name\" has trusted identity \"x509.subject:\" without an identity value")
|
||||||
testCases = append(testCases, testCase{
|
testCases = append(testCases, testCase{
|
||||||
|
@ -298,7 +283,7 @@ func assertNotationVerification(t *testing.T, scheme signature.SigningScheme) {
|
||||||
|
|
||||||
// Expiry Success
|
// Expiry Success
|
||||||
for _, level := range verificationLevels {
|
for _, level := range verificationLevels {
|
||||||
policyDocument := dummyOCIPolicyDocument()
|
policyDocument := dummyPolicyDocument()
|
||||||
testCases = append(testCases, testCase{
|
testCases = append(testCases, testCase{
|
||||||
signatureBlob: validSigEnv,
|
signatureBlob: validSigEnv,
|
||||||
verificationType: trustpolicy.TypeExpiry,
|
verificationType: trustpolicy.TypeExpiry,
|
||||||
|
@ -310,7 +295,7 @@ func assertNotationVerification(t *testing.T, scheme signature.SigningScheme) {
|
||||||
|
|
||||||
// Expiry Failure
|
// Expiry Failure
|
||||||
for _, level := range verificationLevels {
|
for _, level := range verificationLevels {
|
||||||
policyDocument := dummyOCIPolicyDocument()
|
policyDocument := dummyPolicyDocument()
|
||||||
expectedErr := fmt.Errorf("digital signature has expired on \"Fri, 29 Jul 2022 23:59:00 +0000\"")
|
expectedErr := fmt.Errorf("digital signature has expired on \"Fri, 29 Jul 2022 23:59:00 +0000\"")
|
||||||
testCases = append(testCases, testCase{
|
testCases = append(testCases, testCase{
|
||||||
signatureBlob: expiredSigEnv,
|
signatureBlob: expiredSigEnv,
|
||||||
|
@ -342,10 +327,10 @@ func assertNotationVerification(t *testing.T, scheme signature.SigningScheme) {
|
||||||
t.Fatalf("unexpected error while creating revocation object: %v", err)
|
t.Fatalf("unexpected error while creating revocation object: %v", err)
|
||||||
}
|
}
|
||||||
verifier := verifier{
|
verifier := verifier{
|
||||||
ociTrustPolicyDoc: &tt.policyDocument,
|
trustPolicyDoc: &tt.policyDocument,
|
||||||
trustStore: truststore.NewX509TrustStore(dir.ConfigFS()),
|
trustStore: truststore.NewX509TrustStore(dir.ConfigFS()),
|
||||||
pluginManager: pluginManager,
|
pluginManager: pluginManager,
|
||||||
revocationClient: revocationClient,
|
revocationClient: revocationClient,
|
||||||
}
|
}
|
||||||
outcome, _ := verifier.Verify(context.Background(), ocispec.Descriptor{}, tt.signatureBlob, tt.opts)
|
outcome, _ := verifier.Verify(context.Background(), ocispec.Descriptor{}, tt.signatureBlob, tt.opts)
|
||||||
verifyResult(outcome, expectedResult, tt.expectedErr, t)
|
verifyResult(outcome, expectedResult, tt.expectedErr, t)
|
||||||
|
@ -393,7 +378,7 @@ func TestVerifyRevocationEnvelope(t *testing.T) {
|
||||||
|
|
||||||
t.Run("enforced revoked cert", func(t *testing.T) {
|
t.Run("enforced revoked cert", func(t *testing.T) {
|
||||||
testedLevel := trustpolicy.LevelStrict
|
testedLevel := trustpolicy.LevelStrict
|
||||||
policyDoc := dummyOCIPolicyDocument()
|
policyDoc := dummyPolicyDocument()
|
||||||
policyDoc.TrustPolicies[0].SignatureVerification.VerificationLevel = testedLevel.Name
|
policyDoc.TrustPolicies[0].SignatureVerification.VerificationLevel = testedLevel.Name
|
||||||
policyDoc.TrustPolicies[0].SignatureVerification.Override = map[trustpolicy.ValidationType]trustpolicy.ValidationAction{
|
policyDoc.TrustPolicies[0].SignatureVerification.Override = map[trustpolicy.ValidationType]trustpolicy.ValidationAction{
|
||||||
trustpolicy.TypeAuthenticity: trustpolicy.ActionLog,
|
trustpolicy.TypeAuthenticity: trustpolicy.ActionLog,
|
||||||
|
@ -409,10 +394,10 @@ func TestVerifyRevocationEnvelope(t *testing.T) {
|
||||||
dir.UserConfigDir = "testdata"
|
dir.UserConfigDir = "testdata"
|
||||||
|
|
||||||
verifier := verifier{
|
verifier := verifier{
|
||||||
ociTrustPolicyDoc: &policyDoc,
|
trustPolicyDoc: &policyDoc,
|
||||||
trustStore: truststore.NewX509TrustStore(dir.ConfigFS()),
|
trustStore: truststore.NewX509TrustStore(dir.ConfigFS()),
|
||||||
pluginManager: pluginManager,
|
pluginManager: pluginManager,
|
||||||
revocationClient: revocationClient,
|
revocationClient: revocationClient,
|
||||||
}
|
}
|
||||||
outcome, err := verifier.Verify(context.Background(), desc, envelopeBlob, opts)
|
outcome, err := verifier.Verify(context.Background(), desc, envelopeBlob, opts)
|
||||||
if err == nil || err.Error() != expectedErr.Error() {
|
if err == nil || err.Error() != expectedErr.Error() {
|
||||||
|
@ -422,7 +407,7 @@ func TestVerifyRevocationEnvelope(t *testing.T) {
|
||||||
})
|
})
|
||||||
t.Run("log revoked cert", func(t *testing.T) {
|
t.Run("log revoked cert", func(t *testing.T) {
|
||||||
testedLevel := trustpolicy.LevelStrict
|
testedLevel := trustpolicy.LevelStrict
|
||||||
policyDoc := dummyOCIPolicyDocument()
|
policyDoc := dummyPolicyDocument()
|
||||||
policyDoc.TrustPolicies[0].SignatureVerification.VerificationLevel = testedLevel.Name
|
policyDoc.TrustPolicies[0].SignatureVerification.VerificationLevel = testedLevel.Name
|
||||||
policyDoc.TrustPolicies[0].SignatureVerification.Override = map[trustpolicy.ValidationType]trustpolicy.ValidationAction{
|
policyDoc.TrustPolicies[0].SignatureVerification.Override = map[trustpolicy.ValidationType]trustpolicy.ValidationAction{
|
||||||
trustpolicy.TypeAuthenticity: trustpolicy.ActionLog,
|
trustpolicy.TypeAuthenticity: trustpolicy.ActionLog,
|
||||||
|
@ -438,10 +423,10 @@ func TestVerifyRevocationEnvelope(t *testing.T) {
|
||||||
dir.UserConfigDir = "testdata"
|
dir.UserConfigDir = "testdata"
|
||||||
|
|
||||||
verifier := verifier{
|
verifier := verifier{
|
||||||
ociTrustPolicyDoc: &policyDoc,
|
trustPolicyDoc: &policyDoc,
|
||||||
trustStore: truststore.NewX509TrustStore(dir.ConfigFS()),
|
trustStore: truststore.NewX509TrustStore(dir.ConfigFS()),
|
||||||
pluginManager: pluginManager,
|
pluginManager: pluginManager,
|
||||||
revocationClient: revocationClient,
|
revocationClient: revocationClient,
|
||||||
}
|
}
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
outcome, err := verifier.Verify(ctx, desc, envelopeBlob, opts)
|
outcome, err := verifier.Verify(ctx, desc, envelopeBlob, opts)
|
||||||
|
@ -452,7 +437,7 @@ func TestVerifyRevocationEnvelope(t *testing.T) {
|
||||||
})
|
})
|
||||||
t.Run("skip revoked cert", func(t *testing.T) {
|
t.Run("skip revoked cert", func(t *testing.T) {
|
||||||
testedLevel := trustpolicy.LevelStrict
|
testedLevel := trustpolicy.LevelStrict
|
||||||
policyDoc := dummyOCIPolicyDocument()
|
policyDoc := dummyPolicyDocument()
|
||||||
policyDoc.TrustPolicies[0].SignatureVerification.VerificationLevel = testedLevel.Name
|
policyDoc.TrustPolicies[0].SignatureVerification.VerificationLevel = testedLevel.Name
|
||||||
policyDoc.TrustPolicies[0].SignatureVerification.Override = map[trustpolicy.ValidationType]trustpolicy.ValidationAction{
|
policyDoc.TrustPolicies[0].SignatureVerification.Override = map[trustpolicy.ValidationType]trustpolicy.ValidationAction{
|
||||||
trustpolicy.TypeAuthenticity: trustpolicy.ActionLog,
|
trustpolicy.TypeAuthenticity: trustpolicy.ActionLog,
|
||||||
|
@ -462,10 +447,10 @@ func TestVerifyRevocationEnvelope(t *testing.T) {
|
||||||
dir.UserConfigDir = "testdata"
|
dir.UserConfigDir = "testdata"
|
||||||
|
|
||||||
verifier := verifier{
|
verifier := verifier{
|
||||||
ociTrustPolicyDoc: &policyDoc,
|
trustPolicyDoc: &policyDoc,
|
||||||
trustStore: truststore.NewX509TrustStore(dir.ConfigFS()),
|
trustStore: truststore.NewX509TrustStore(dir.ConfigFS()),
|
||||||
pluginManager: pluginManager,
|
pluginManager: pluginManager,
|
||||||
revocationClient: revocationClient,
|
revocationClient: revocationClient,
|
||||||
}
|
}
|
||||||
outcome, err := verifier.Verify(context.Background(), desc, envelopeBlob, opts)
|
outcome, err := verifier.Verify(context.Background(), desc, envelopeBlob, opts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -714,92 +699,56 @@ func TestVerifyRevocation(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNew(t *testing.T) {
|
func TestNew(t *testing.T) {
|
||||||
if _, err := New(&ociPolicy, store, pm); err != nil {
|
if _, err := New(&policy, store, pm); err != nil {
|
||||||
t.Fatalf("expected New constructor to succeed, but got %v", err)
|
t.Fatalf("expected New constructor to succeed, but got %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNewWithOptions(t *testing.T) {
|
func TestNewWithOptions(t *testing.T) {
|
||||||
if _, err := NewWithOptions(&ociPolicy, store, pm, VerifierOptions{}); err != nil {
|
if _, err := NewWithOptions(&policy, store, pm, VerifierOptions{}); err != nil {
|
||||||
t.Fatalf("expected NewWithOptions constructor to succeed, but got %v", err)
|
t.Fatalf("expected NewWithOptions constructor to succeed, but got %v", err)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
func TestNewVerifierWithOptions(t *testing.T) {
|
|
||||||
r, err := revocation.New(&http.Client{})
|
r, err := revocation.New(&http.Client{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unexpected error while creating revocation object: %v", err)
|
t.Fatalf("unexpected error while creating revocation object: %v", err)
|
||||||
}
|
}
|
||||||
|
opts := VerifierOptions{RevocationClient: r}
|
||||||
|
|
||||||
v, err := NewVerifierWithOptions(store, VerifierOptions{
|
_, err = NewWithOptions(&policy, store, pm, opts)
|
||||||
RevocationClient: r,
|
|
||||||
OCITrustPolicy: &ociPolicy,
|
|
||||||
BlobTrustPolicy: &blobPolicy,
|
|
||||||
PluginManager: pm,
|
|
||||||
})
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("expected NewVerifierWithOptions constructor to succeed, but got %v", err)
|
t.Fatalf("expected NewWithOptions constructor to succeed, but got %v", err)
|
||||||
}
|
|
||||||
if !(v.ociTrustPolicyDoc == &ociPolicy) {
|
|
||||||
t.Fatalf("expected ociTrustPolicyDoc %v, but got %v", v, v.ociTrustPolicyDoc)
|
|
||||||
}
|
|
||||||
if !(v.trustStore == store) {
|
|
||||||
t.Fatalf("expected trustStore %v, but got %v", store, v.trustStore)
|
|
||||||
}
|
|
||||||
if !reflect.DeepEqual(v.pluginManager, pm) {
|
|
||||||
t.Fatalf("expected pluginManager %v, but got %v", pm, v.pluginManager)
|
|
||||||
}
|
|
||||||
if v.revocationClient == nil {
|
|
||||||
t.Fatal("expected nonnil revocationClient")
|
|
||||||
}
|
|
||||||
if v.revocationCodeSigningValidator != nil {
|
|
||||||
t.Fatal("expected nil revocationCodeSigningValidator")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = NewVerifierWithOptions(store, VerifierOptions{
|
revocationCodeSigningValidator, err := revocation.NewWithOptions(revocation.Options{
|
||||||
RevocationClient: r,
|
OCSPHTTPClient: &http.Client{},
|
||||||
BlobTrustPolicy: &blobPolicy,
|
CertChainPurpose: purpose.CodeSigning,
|
||||||
PluginManager: pm,
|
|
||||||
})
|
})
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("expected NewVerifierWithOptions constructor to succeed, but got %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = NewVerifierWithOptions(store, VerifierOptions{
|
|
||||||
RevocationClient: r,
|
|
||||||
OCITrustPolicy: &ociPolicy,
|
|
||||||
PluginManager: pm,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("expected NewVerifierWithOptions constructor to succeed, but got %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = NewVerifierWithOptions(store, VerifierOptions{
|
|
||||||
OCITrustPolicy: &ociPolicy,
|
|
||||||
PluginManager: pm,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("expected NewVerifierWithOptions constructor to succeed, but got %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
csValidator, err := revocation.NewWithOptions(revocation.Options{})
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
v, err = NewVerifierWithOptions(store, VerifierOptions{
|
revocationTimestampingValidator, err := revocation.NewWithOptions(revocation.Options{
|
||||||
RevocationCodeSigningValidator: csValidator,
|
OCSPHTTPClient: &http.Client{},
|
||||||
OCITrustPolicy: &ociPolicy,
|
CertChainPurpose: purpose.Timestamping,
|
||||||
PluginManager: pm,
|
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("expected NewVerifierWithOptions constructor to succeed, but got %v", err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
if v.revocationCodeSigningValidator == nil {
|
opts.RevocationCodeSigningValidator = revocationCodeSigningValidator
|
||||||
t.Fatal("expected v.revocationCodeSigningValidator to be non-nil")
|
opts.RevocationTimestampingValidator = revocationTimestampingValidator
|
||||||
|
_, err = NewWithOptions(&policy, store, pm, opts)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("expected NewWithOptions constructor to succeed, but got %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
opts.RevocationClient = nil
|
||||||
|
_, err = NewWithOptions(&policy, store, pm, opts)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("expected NewWithOptions constructor to succeed, but got %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNewVerifierWithOptionsError(t *testing.T) {
|
func TestNewWithOptionsError(t *testing.T) {
|
||||||
r, err := revocation.New(&http.Client{})
|
r, err := revocation.New(&http.Client{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unexpected error while creating revocation object: %v", err)
|
t.Fatalf("unexpected error while creating revocation object: %v", err)
|
||||||
|
@ -811,174 +760,28 @@ func TestNewVerifierWithOptionsError(t *testing.T) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unexpected error while creating revocation timestamp object: %v", err)
|
t.Fatalf("unexpected error while creating revocation timestamp object: %v", err)
|
||||||
}
|
}
|
||||||
|
opts := VerifierOptions{
|
||||||
_, err = NewVerifierWithOptions(store, VerifierOptions{
|
|
||||||
RevocationClient: r,
|
RevocationClient: r,
|
||||||
RevocationTimestampingValidator: rt,
|
RevocationTimestampingValidator: rt,
|
||||||
PluginManager: pm,
|
|
||||||
})
|
|
||||||
if err == nil || err.Error() != "ociTrustPolicy and blobTrustPolicy both cannot be nil" {
|
|
||||||
t.Errorf("expected err but not found.")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = NewVerifierWithOptions(nil, VerifierOptions{
|
_, err = NewWithOptions(nil, store, pm, opts)
|
||||||
RevocationClient: r,
|
expectedErrMsg := "trustPolicy cannot be nil"
|
||||||
RevocationTimestampingValidator: rt,
|
if err == nil || err.Error() != expectedErrMsg {
|
||||||
OCITrustPolicy: &ociPolicy,
|
t.Errorf("expected %s, but got %s", expectedErrMsg, err)
|
||||||
BlobTrustPolicy: &blobPolicy,
|
|
||||||
PluginManager: pm,
|
|
||||||
})
|
|
||||||
if err == nil || err.Error() != "trustStore cannot be nil" {
|
|
||||||
t.Errorf("expected err but not found.")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestNewOCIVerifierFromConfig(t *testing.T) {
|
|
||||||
defer func(oldUserConfigDir string) {
|
|
||||||
dir.UserConfigDir = oldUserConfigDir
|
|
||||||
}(dir.UserConfigDir)
|
|
||||||
|
|
||||||
tempRoot := t.TempDir()
|
|
||||||
dir.UserConfigDir = tempRoot
|
|
||||||
path := filepath.Join(tempRoot, "trustpolicy.oci.json")
|
|
||||||
policyJson, _ := json.Marshal(dummyOCIPolicyDocument())
|
|
||||||
if err := os.WriteFile(path, policyJson, 0600); err != nil {
|
|
||||||
t.Fatalf("TestLoadOCIDocument write policy file failed. Error: %v", err)
|
|
||||||
}
|
|
||||||
t.Cleanup(func() { os.RemoveAll(tempRoot) })
|
|
||||||
|
|
||||||
_, err := NewOCIVerifierFromConfig()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("expected NewOCIVerifierFromConfig constructor to succeed, but got %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestNewBlobVerifierFromConfig(t *testing.T) {
|
|
||||||
defer func(oldUserConfigDir string) {
|
|
||||||
dir.UserConfigDir = oldUserConfigDir
|
|
||||||
}(dir.UserConfigDir)
|
|
||||||
|
|
||||||
tempRoot := t.TempDir()
|
|
||||||
dir.UserConfigDir = tempRoot
|
|
||||||
path := filepath.Join(tempRoot, "trustpolicy.blob.json")
|
|
||||||
policyJson, _ := json.Marshal(dummyBlobPolicyDocument())
|
|
||||||
if err := os.WriteFile(path, policyJson, 0600); err != nil {
|
|
||||||
t.Fatalf("TestLoadBlobDocument write policy file failed. Error: %v", err)
|
|
||||||
}
|
|
||||||
t.Cleanup(func() { os.RemoveAll(tempRoot) })
|
|
||||||
|
|
||||||
_, err := NewBlobVerifierFromConfig()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("expected NewBlobVerifierFromConfig constructor to succeed, but got %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestVerifyBlob(t *testing.T) {
|
|
||||||
policy := &trustpolicy.BlobDocument{
|
|
||||||
Version: "1.0",
|
|
||||||
TrustPolicies: []trustpolicy.BlobTrustPolicy{
|
|
||||||
{
|
|
||||||
Name: "blob-test-policy",
|
|
||||||
SignatureVerification: trustpolicy.SignatureVerification{VerificationLevel: "strict"},
|
|
||||||
TrustStores: []string{"ca:dummy-ts"},
|
|
||||||
TrustedIdentities: []string{"*"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
v, err := NewVerifierWithOptions(&testTrustStore{}, VerifierOptions{
|
|
||||||
BlobTrustPolicy: policy,
|
|
||||||
PluginManager: pm,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unexpected error while creating verifier: %v", err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
opts := notation.BlobVerifierVerifyOptions{
|
_, err = NewWithOptions(&policy, nil, pm, opts)
|
||||||
SignatureMediaType: jws.MediaTypeEnvelope,
|
expectedErrMsg = "trustStore cannot be nil"
|
||||||
TrustPolicyName: "blob-test-policy",
|
if err == nil || err.Error() != expectedErrMsg {
|
||||||
}
|
t.Errorf("expected %s, but got %s", expectedErrMsg, err)
|
||||||
descGenFunc := getTestDescGenFunc(false, "")
|
|
||||||
|
|
||||||
t.Run("without user defined metadata", func(t *testing.T) {
|
|
||||||
// verify with
|
|
||||||
if _, err = v.VerifyBlob(context.Background(), descGenFunc, []byte(testSig), opts); err != nil {
|
|
||||||
t.Fatalf("VerifyBlob() returned unexpected error: %v", err)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("with user defined metadata", func(t *testing.T) {
|
|
||||||
opts.UserMetadata = map[string]string{"buildId": "101"}
|
|
||||||
if _, err = v.VerifyBlob(context.Background(), descGenFunc, []byte(testSig), opts); err != nil {
|
|
||||||
t.Fatalf("VerifyBlob() with user metadata returned unexpected error: %v", err)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("trust policy set to skip", func(t *testing.T) {
|
|
||||||
policy.TrustPolicies[0].SignatureVerification = trustpolicy.SignatureVerification{VerificationLevel: "skip"}
|
|
||||||
opts.UserMetadata = map[string]string{"buildId": "101"}
|
|
||||||
if _, err = v.VerifyBlob(context.Background(), descGenFunc, []byte(testSig), opts); err != nil {
|
|
||||||
t.Fatalf("VerifyBlob() with user metadata returned unexpected error: %v", err)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestVerifyBlob_Error(t *testing.T) {
|
|
||||||
policy := &trustpolicy.BlobDocument{
|
|
||||||
Version: "1.0",
|
|
||||||
TrustPolicies: []trustpolicy.BlobTrustPolicy{
|
|
||||||
{
|
|
||||||
Name: "blob-test-policy",
|
|
||||||
SignatureVerification: trustpolicy.SignatureVerification{VerificationLevel: "strict"},
|
|
||||||
TrustStores: []string{"ca:dummy-ts"},
|
|
||||||
TrustedIdentities: []string{"*"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
v, err := NewVerifierWithOptions(&testTrustStore{}, VerifierOptions{
|
|
||||||
BlobTrustPolicy: policy,
|
|
||||||
PluginManager: pm,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unexpected error while creating verifier: %v", err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
opts := notation.BlobVerifierVerifyOptions{
|
_, err = NewWithOptions(&invalidPolicy, store, pm, opts)
|
||||||
SignatureMediaType: jws.MediaTypeEnvelope,
|
expectedErrMsg = "trust policy document has empty version, version must be specified"
|
||||||
TrustPolicyName: "blob-test-policy",
|
if err == nil || err.Error() != expectedErrMsg {
|
||||||
|
t.Errorf("expected %s, but got %s", expectedErrMsg, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
t.Run("BlobDescriptorGenerator returns error", func(t *testing.T) {
|
|
||||||
descGenFunc := getTestDescGenFunc(true, "")
|
|
||||||
_, err = v.VerifyBlob(context.Background(), descGenFunc, []byte(testSig), opts)
|
|
||||||
if err == nil || err.Error() != "failed to generate descriptor for given artifact. Error: intentional test desc generation error" {
|
|
||||||
t.Errorf("VerifyBlob() didn't return error or didnt returned expected error: %v", err)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("descriptor mismatch returns error", func(t *testing.T) {
|
|
||||||
descGenFunc := getTestDescGenFunc(false, "sha384:b8ab24dafba5cf7e4c89c562f811cf10493d4203da982d3b1345f366ca863d9c2ed323dbd0fb7ff83a80302ceffa5a62")
|
|
||||||
_, err = v.VerifyBlob(context.Background(), descGenFunc, []byte(testSig), opts)
|
|
||||||
if err == nil || err.Error() != "integrity check failed. signature does not match the given blob" {
|
|
||||||
t.Errorf("VerifyBlob() didn't return error or didnt returned expected error: %v", err)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("signature malformed returns error", func(t *testing.T) {
|
|
||||||
descGenFunc := getTestDescGenFunc(false, "")
|
|
||||||
_, err = v.VerifyBlob(context.Background(), descGenFunc, []byte(""), opts)
|
|
||||||
if err == nil || err.Error() != "unable to parse the digital signature, error : unexpected end of JSON input" {
|
|
||||||
t.Errorf("VerifyBlob() didn't return error or didnt returned expected error: %v", err)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("user defined metadata mismatch returns error", func(t *testing.T) {
|
|
||||||
descGenFunc := getTestDescGenFunc(false, "")
|
|
||||||
opts.UserMetadata = map[string]string{"buildId": "zzz"}
|
|
||||||
_, err = v.VerifyBlob(context.Background(), descGenFunc, []byte(testSig), opts)
|
|
||||||
if err == nil || err.Error() != "unable to find specified metadata in the signature" {
|
|
||||||
t.Fatalf("VerifyBlob() with user metadata returned unexpected error: %v", err)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestVerificationPluginInteractions(t *testing.T) {
|
func TestVerificationPluginInteractions(t *testing.T) {
|
||||||
|
@ -994,7 +797,7 @@ func assertPluginVerification(scheme signature.SigningScheme, t *testing.T) {
|
||||||
pluginSigEnv = mock.MockSaPluginSigEnv
|
pluginSigEnv = mock.MockSaPluginSigEnv
|
||||||
}
|
}
|
||||||
|
|
||||||
policyDocument := dummyOCIPolicyDocument()
|
policyDocument := dummyPolicyDocument()
|
||||||
dir.UserConfigDir = "testdata"
|
dir.UserConfigDir = "testdata"
|
||||||
x509TrustStore := truststore.NewX509TrustStore(dir.ConfigFS())
|
x509TrustStore := truststore.NewX509TrustStore(dir.ConfigFS())
|
||||||
|
|
||||||
|
@ -1007,10 +810,10 @@ func assertPluginVerification(scheme signature.SigningScheme, t *testing.T) {
|
||||||
t.Fatalf("unexpected error while creating revocation object: %v", err)
|
t.Fatalf("unexpected error while creating revocation object: %v", err)
|
||||||
}
|
}
|
||||||
v := verifier{
|
v := verifier{
|
||||||
ociTrustPolicyDoc: &policyDocument,
|
trustPolicyDoc: &policyDocument,
|
||||||
trustStore: x509TrustStore,
|
trustStore: x509TrustStore,
|
||||||
pluginManager: pluginManager,
|
pluginManager: pluginManager,
|
||||||
revocationClient: revocationClient,
|
revocationClient: revocationClient,
|
||||||
}
|
}
|
||||||
opts := notation.VerifierVerifyOptions{ArtifactReference: mock.SampleArtifactUri, SignatureMediaType: "application/jose+json"}
|
opts := notation.VerifierVerifyOptions{ArtifactReference: mock.SampleArtifactUri, SignatureMediaType: "application/jose+json"}
|
||||||
outcome, err := v.Verify(context.Background(), ocispec.Descriptor{}, pluginSigEnv, opts)
|
outcome, err := v.Verify(context.Background(), ocispec.Descriptor{}, pluginSigEnv, opts)
|
||||||
|
@ -1023,10 +826,10 @@ func assertPluginVerification(scheme signature.SigningScheme, t *testing.T) {
|
||||||
pluginManager.PluginCapabilities = []proto.Capability{proto.CapabilitySignatureGenerator}
|
pluginManager.PluginCapabilities = []proto.Capability{proto.CapabilitySignatureGenerator}
|
||||||
|
|
||||||
v = verifier{
|
v = verifier{
|
||||||
ociTrustPolicyDoc: &policyDocument,
|
trustPolicyDoc: &policyDocument,
|
||||||
trustStore: x509TrustStore,
|
trustStore: x509TrustStore,
|
||||||
pluginManager: pluginManager,
|
pluginManager: pluginManager,
|
||||||
revocationClient: revocationClient,
|
revocationClient: revocationClient,
|
||||||
}
|
}
|
||||||
opts = notation.VerifierVerifyOptions{ArtifactReference: mock.SampleArtifactUri, SignatureMediaType: "application/jose+json"}
|
opts = notation.VerifierVerifyOptions{ArtifactReference: mock.SampleArtifactUri, SignatureMediaType: "application/jose+json"}
|
||||||
outcome, err = v.Verify(context.Background(), ocispec.Descriptor{}, pluginSigEnv, opts)
|
outcome, err = v.Verify(context.Background(), ocispec.Descriptor{}, pluginSigEnv, opts)
|
||||||
|
@ -1047,10 +850,10 @@ func assertPluginVerification(scheme signature.SigningScheme, t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
v = verifier{
|
v = verifier{
|
||||||
ociTrustPolicyDoc: &policyDocument,
|
trustPolicyDoc: &policyDocument,
|
||||||
trustStore: x509TrustStore,
|
trustStore: x509TrustStore,
|
||||||
pluginManager: pluginManager,
|
pluginManager: pluginManager,
|
||||||
revocationClient: revocationClient,
|
revocationClient: revocationClient,
|
||||||
}
|
}
|
||||||
opts = notation.VerifierVerifyOptions{ArtifactReference: mock.SampleArtifactUri, SignatureMediaType: "application/jose+json"}
|
opts = notation.VerifierVerifyOptions{ArtifactReference: mock.SampleArtifactUri, SignatureMediaType: "application/jose+json"}
|
||||||
outcome, err = v.Verify(context.Background(), mock.ImageDescriptor, pluginSigEnv, opts)
|
outcome, err = v.Verify(context.Background(), mock.ImageDescriptor, pluginSigEnv, opts)
|
||||||
|
@ -1072,10 +875,10 @@ func assertPluginVerification(scheme signature.SigningScheme, t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
v = verifier{
|
v = verifier{
|
||||||
ociTrustPolicyDoc: &policyDocument,
|
trustPolicyDoc: &policyDocument,
|
||||||
trustStore: x509TrustStore,
|
trustStore: x509TrustStore,
|
||||||
pluginManager: pluginManager,
|
pluginManager: pluginManager,
|
||||||
revocationClient: revocationClient,
|
revocationClient: revocationClient,
|
||||||
}
|
}
|
||||||
opts = notation.VerifierVerifyOptions{ArtifactReference: mock.SampleArtifactUri, SignatureMediaType: "application/jose+json"}
|
opts = notation.VerifierVerifyOptions{ArtifactReference: mock.SampleArtifactUri, SignatureMediaType: "application/jose+json"}
|
||||||
outcome, err = v.Verify(context.Background(), ocispec.Descriptor{}, pluginSigEnv, opts)
|
outcome, err = v.Verify(context.Background(), ocispec.Descriptor{}, pluginSigEnv, opts)
|
||||||
|
@ -1096,10 +899,10 @@ func assertPluginVerification(scheme signature.SigningScheme, t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
v = verifier{
|
v = verifier{
|
||||||
ociTrustPolicyDoc: &policyDocument,
|
trustPolicyDoc: &policyDocument,
|
||||||
trustStore: x509TrustStore,
|
trustStore: x509TrustStore,
|
||||||
pluginManager: pluginManager,
|
pluginManager: pluginManager,
|
||||||
revocationClient: revocationClient,
|
revocationClient: revocationClient,
|
||||||
}
|
}
|
||||||
opts = notation.VerifierVerifyOptions{ArtifactReference: mock.SampleArtifactUri, SignatureMediaType: "application/jose+json"}
|
opts = notation.VerifierVerifyOptions{ArtifactReference: mock.SampleArtifactUri, SignatureMediaType: "application/jose+json"}
|
||||||
outcome, err = v.Verify(context.Background(), mock.ImageDescriptor, pluginSigEnv, opts)
|
outcome, err = v.Verify(context.Background(), mock.ImageDescriptor, pluginSigEnv, opts)
|
||||||
|
@ -1121,10 +924,10 @@ func assertPluginVerification(scheme signature.SigningScheme, t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
v = verifier{
|
v = verifier{
|
||||||
ociTrustPolicyDoc: &policyDocument,
|
trustPolicyDoc: &policyDocument,
|
||||||
trustStore: x509TrustStore,
|
trustStore: x509TrustStore,
|
||||||
pluginManager: pluginManager,
|
pluginManager: pluginManager,
|
||||||
revocationClient: revocationClient,
|
revocationClient: revocationClient,
|
||||||
}
|
}
|
||||||
opts = notation.VerifierVerifyOptions{ArtifactReference: mock.SampleArtifactUri, SignatureMediaType: "application/jose+json"}
|
opts = notation.VerifierVerifyOptions{ArtifactReference: mock.SampleArtifactUri, SignatureMediaType: "application/jose+json"}
|
||||||
outcome, err = v.Verify(context.Background(), ocispec.Descriptor{}, pluginSigEnv, opts)
|
outcome, err = v.Verify(context.Background(), ocispec.Descriptor{}, pluginSigEnv, opts)
|
||||||
|
@ -1148,10 +951,10 @@ func assertPluginVerification(scheme signature.SigningScheme, t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
v = verifier{
|
v = verifier{
|
||||||
ociTrustPolicyDoc: &policyDocument,
|
trustPolicyDoc: &policyDocument,
|
||||||
trustStore: x509TrustStore,
|
trustStore: x509TrustStore,
|
||||||
pluginManager: pluginManager,
|
pluginManager: pluginManager,
|
||||||
revocationClient: revocationClient,
|
revocationClient: revocationClient,
|
||||||
}
|
}
|
||||||
opts = notation.VerifierVerifyOptions{ArtifactReference: mock.SampleArtifactUri, SignatureMediaType: "application/jose+json"}
|
opts = notation.VerifierVerifyOptions{ArtifactReference: mock.SampleArtifactUri, SignatureMediaType: "application/jose+json"}
|
||||||
outcome, err = v.Verify(context.Background(), mock.ImageDescriptor, pluginSigEnv, opts)
|
outcome, err = v.Verify(context.Background(), mock.ImageDescriptor, pluginSigEnv, opts)
|
||||||
|
@ -1166,10 +969,10 @@ func assertPluginVerification(scheme signature.SigningScheme, t *testing.T) {
|
||||||
pluginManager.PluginRunnerExecuteError = errors.New("revocation plugin should not be invoked when the trust policy skips revocation check")
|
pluginManager.PluginRunnerExecuteError = errors.New("revocation plugin should not be invoked when the trust policy skips revocation check")
|
||||||
|
|
||||||
v = verifier{
|
v = verifier{
|
||||||
ociTrustPolicyDoc: &policyDocument,
|
trustPolicyDoc: &policyDocument,
|
||||||
trustStore: x509TrustStore,
|
trustStore: x509TrustStore,
|
||||||
pluginManager: pluginManager,
|
pluginManager: pluginManager,
|
||||||
revocationClient: revocationClient,
|
revocationClient: revocationClient,
|
||||||
}
|
}
|
||||||
opts = notation.VerifierVerifyOptions{ArtifactReference: mock.SampleArtifactUri, SignatureMediaType: "application/jose+json"}
|
opts = notation.VerifierVerifyOptions{ArtifactReference: mock.SampleArtifactUri, SignatureMediaType: "application/jose+json"}
|
||||||
trustPolicy, err := (&policyDocument).GetApplicableTrustPolicy(opts.ArtifactReference)
|
trustPolicy, err := (&policyDocument).GetApplicableTrustPolicy(opts.ArtifactReference)
|
||||||
|
@ -1193,10 +996,10 @@ func assertPluginVerification(scheme signature.SigningScheme, t *testing.T) {
|
||||||
pluginManager.PluginRunnerExecuteError = errors.New("invalid plugin response")
|
pluginManager.PluginRunnerExecuteError = errors.New("invalid plugin response")
|
||||||
|
|
||||||
v = verifier{
|
v = verifier{
|
||||||
ociTrustPolicyDoc: &policyDocument,
|
trustPolicyDoc: &policyDocument,
|
||||||
trustStore: x509TrustStore,
|
trustStore: x509TrustStore,
|
||||||
pluginManager: pluginManager,
|
pluginManager: pluginManager,
|
||||||
revocationClient: revocationClient,
|
revocationClient: revocationClient,
|
||||||
}
|
}
|
||||||
opts = notation.VerifierVerifyOptions{ArtifactReference: mock.SampleArtifactUri, SignatureMediaType: "application/jose+json"}
|
opts = notation.VerifierVerifyOptions{ArtifactReference: mock.SampleArtifactUri, SignatureMediaType: "application/jose+json"}
|
||||||
trustPolicy, err = (&policyDocument).GetApplicableTrustPolicy(opts.ArtifactReference)
|
trustPolicy, err = (&policyDocument).GetApplicableTrustPolicy(opts.ArtifactReference)
|
||||||
|
@ -1226,10 +1029,10 @@ func assertPluginVerification(scheme signature.SigningScheme, t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
v = verifier{
|
v = verifier{
|
||||||
ociTrustPolicyDoc: &policyDocument,
|
trustPolicyDoc: &policyDocument,
|
||||||
trustStore: x509TrustStore,
|
trustStore: x509TrustStore,
|
||||||
pluginManager: pluginManager,
|
pluginManager: pluginManager,
|
||||||
revocationClient: revocationClient,
|
revocationClient: revocationClient,
|
||||||
}
|
}
|
||||||
opts = notation.VerifierVerifyOptions{ArtifactReference: mock.SampleArtifactUri, SignatureMediaType: "application/jose+json"}
|
opts = notation.VerifierVerifyOptions{ArtifactReference: mock.SampleArtifactUri, SignatureMediaType: "application/jose+json"}
|
||||||
outcome, err = v.Verify(context.Background(), mock.ImageDescriptor, pluginSigEnv, opts)
|
outcome, err = v.Verify(context.Background(), mock.ImageDescriptor, pluginSigEnv, opts)
|
||||||
|
@ -1246,10 +1049,10 @@ func assertPluginVerification(scheme signature.SigningScheme, t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
v = verifier{
|
v = verifier{
|
||||||
ociTrustPolicyDoc: &policyDocument,
|
trustPolicyDoc: &policyDocument,
|
||||||
trustStore: x509TrustStore,
|
trustStore: x509TrustStore,
|
||||||
pluginManager: pluginManager,
|
pluginManager: pluginManager,
|
||||||
revocationClient: revocationClient,
|
revocationClient: revocationClient,
|
||||||
}
|
}
|
||||||
opts = notation.VerifierVerifyOptions{ArtifactReference: mock.SampleArtifactUri, SignatureMediaType: "application/jose+json"}
|
opts = notation.VerifierVerifyOptions{ArtifactReference: mock.SampleArtifactUri, SignatureMediaType: "application/jose+json"}
|
||||||
outcome, err = v.Verify(context.Background(), mock.ImageDescriptor, pluginSigEnv, opts)
|
outcome, err = v.Verify(context.Background(), mock.ImageDescriptor, pluginSigEnv, opts)
|
||||||
|
@ -1297,7 +1100,7 @@ func TestVerifyX509TrustedIdentities(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestVerifyUserMetadata(t *testing.T) {
|
func TestVerifyUserMetadata(t *testing.T) {
|
||||||
policyDocument := dummyOCIPolicyDocument()
|
policyDocument := dummyPolicyDocument()
|
||||||
policyDocument.TrustPolicies[0].SignatureVerification.VerificationLevel = trustpolicy.LevelAudit.Name
|
policyDocument.TrustPolicies[0].SignatureVerification.VerificationLevel = trustpolicy.LevelAudit.Name
|
||||||
|
|
||||||
pluginManager := mock.PluginManager{}
|
pluginManager := mock.PluginManager{}
|
||||||
|
@ -1308,10 +1111,10 @@ func TestVerifyUserMetadata(t *testing.T) {
|
||||||
t.Fatalf("unexpected error while creating revocation object: %v", err)
|
t.Fatalf("unexpected error while creating revocation object: %v", err)
|
||||||
}
|
}
|
||||||
verifier := verifier{
|
verifier := verifier{
|
||||||
ociTrustPolicyDoc: &policyDocument,
|
trustPolicyDoc: &policyDocument,
|
||||||
trustStore: truststore.NewX509TrustStore(dir.ConfigFS()),
|
trustStore: truststore.NewX509TrustStore(dir.ConfigFS()),
|
||||||
pluginManager: pluginManager,
|
pluginManager: pluginManager,
|
||||||
revocationClient: revocationClient,
|
revocationClient: revocationClient,
|
||||||
}
|
}
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
|
@ -1377,10 +1180,10 @@ func TestPluginVersionCompatibility(t *testing.T) {
|
||||||
t.Fatalf("unexpected error while creating revocation object: %v", err)
|
t.Fatalf("unexpected error while creating revocation object: %v", err)
|
||||||
}
|
}
|
||||||
v := verifier{
|
v := verifier{
|
||||||
ociTrustPolicyDoc: &policyDocument,
|
trustPolicyDoc: &policyDocument,
|
||||||
trustStore: x509TrustStore,
|
trustStore: x509TrustStore,
|
||||||
pluginManager: pluginManager,
|
pluginManager: pluginManager,
|
||||||
revocationClient: revocationClient,
|
revocationClient: revocationClient,
|
||||||
}
|
}
|
||||||
opts := notation.VerifierVerifyOptions{ArtifactReference: "localhost:5000/net-monitor@sha256:fe7e9333395060c2f5e63cf36a38fba10176f183b4163a5794e081a480abba5f", SignatureMediaType: "application/jose+json"}
|
opts := notation.VerifierVerifyOptions{ArtifactReference: "localhost:5000/net-monitor@sha256:fe7e9333395060c2f5e63cf36a38fba10176f183b4163a5794e081a480abba5f", SignatureMediaType: "application/jose+json"}
|
||||||
|
|
||||||
|
@ -1432,120 +1235,6 @@ func TestIsRequiredVerificationPluginVer(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRevocationFinalResult(t *testing.T) {
|
|
||||||
certResult := []*revocationresult.CertRevocationResult{
|
|
||||||
{
|
|
||||||
// update leaf cert result in each sub-test
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Result: revocationresult.ResultNonRevokable,
|
|
||||||
ServerResults: []*revocationresult.ServerResult{
|
|
||||||
{
|
|
||||||
Result: revocationresult.ResultNonRevokable,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
certChain := []*x509.Certificate{
|
|
||||||
{
|
|
||||||
Subject: pkix.Name{
|
|
||||||
CommonName: "leafCert",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Subject: pkix.Name{
|
|
||||||
CommonName: "rootCert",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
t.Run("OCSP error without fallback", func(t *testing.T) {
|
|
||||||
certResult[0] = &revocationresult.CertRevocationResult{
|
|
||||||
Result: revocationresult.ResultUnknown,
|
|
||||||
ServerResults: []*revocationresult.ServerResult{
|
|
||||||
{
|
|
||||||
Server: "http://ocsp.example.com",
|
|
||||||
Result: revocationresult.ResultUnknown,
|
|
||||||
Error: errors.New("ocsp error"),
|
|
||||||
RevocationMethod: result.RevocationMethodOCSP,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
finalResult, problematicCertSubject := revocationFinalResult(certResult, certChain, log.Discard)
|
|
||||||
if finalResult != revocationresult.ResultUnknown || problematicCertSubject != "CN=leafCert" {
|
|
||||||
t.Fatalf("unexpected final result: %v, problematic cert subject: %s", finalResult, problematicCertSubject)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("OCSP error with fallback", func(t *testing.T) {
|
|
||||||
certResult[0] = &revocationresult.CertRevocationResult{
|
|
||||||
Result: revocationresult.ResultOK,
|
|
||||||
ServerResults: []*revocationresult.ServerResult{
|
|
||||||
{
|
|
||||||
Server: "http://ocsp.example.com",
|
|
||||||
Result: revocationresult.ResultUnknown,
|
|
||||||
Error: errors.New("ocsp error"),
|
|
||||||
RevocationMethod: result.RevocationMethodOCSP,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Result: revocationresult.ResultOK,
|
|
||||||
Server: "http://crl.example.com",
|
|
||||||
RevocationMethod: result.RevocationMethodCRL,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
RevocationMethod: result.RevocationMethodOCSPFallbackCRL,
|
|
||||||
}
|
|
||||||
|
|
||||||
finalResult, problematicCertSubject := revocationFinalResult(certResult, certChain, log.Discard)
|
|
||||||
if finalResult != revocationresult.ResultOK || problematicCertSubject != "" {
|
|
||||||
t.Fatalf("unexpected final result: %v, problematic cert subject: %s", finalResult, problematicCertSubject)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("OCSP error with fallback and CRL error", func(t *testing.T) {
|
|
||||||
certResult[0] = &revocationresult.CertRevocationResult{
|
|
||||||
Result: revocationresult.ResultUnknown,
|
|
||||||
ServerResults: []*revocationresult.ServerResult{
|
|
||||||
{
|
|
||||||
Server: "http://ocsp.example.com",
|
|
||||||
Result: revocationresult.ResultUnknown,
|
|
||||||
Error: errors.New("ocsp error"),
|
|
||||||
RevocationMethod: result.RevocationMethodOCSP,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Result: revocationresult.ResultUnknown,
|
|
||||||
Error: errors.New("crl error"),
|
|
||||||
RevocationMethod: result.RevocationMethodCRL,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
RevocationMethod: result.RevocationMethodOCSPFallbackCRL,
|
|
||||||
}
|
|
||||||
|
|
||||||
finalResult, problematicCertSubject := revocationFinalResult(certResult, certChain, log.Discard)
|
|
||||||
if finalResult != revocationresult.ResultUnknown || problematicCertSubject != "CN=leafCert" {
|
|
||||||
t.Fatalf("unexpected final result: %v, problematic cert subject: %s", finalResult, problematicCertSubject)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("revocation method unknown error(should never reach here)", func(t *testing.T) {
|
|
||||||
certResult[0] = &revocationresult.CertRevocationResult{
|
|
||||||
Result: revocationresult.ResultUnknown,
|
|
||||||
ServerResults: []*revocationresult.ServerResult{
|
|
||||||
{
|
|
||||||
Result: revocationresult.ResultUnknown,
|
|
||||||
Error: errors.New("unknown error"),
|
|
||||||
RevocationMethod: result.RevocationMethodUnknown,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
finalResult, problematicCertSubject := revocationFinalResult(certResult, certChain, log.Discard)
|
|
||||||
if finalResult != revocationresult.ResultUnknown || problematicCertSubject != "CN=leafCert" {
|
|
||||||
t.Fatalf("unexpected final result: %v, problematic cert subject: %s", finalResult, problematicCertSubject)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func verifyResult(outcome *notation.VerificationOutcome, expectedResult notation.ValidationResult, expectedErr error, t *testing.T) {
|
func verifyResult(outcome *notation.VerificationOutcome, expectedResult notation.ValidationResult, expectedErr error, t *testing.T) {
|
||||||
var actualResult *notation.ValidationResult
|
var actualResult *notation.ValidationResult
|
||||||
for _, r := range outcome.VerificationResults {
|
for _, r := range outcome.VerificationResults {
|
||||||
|
@ -1568,32 +1257,3 @@ func verifyResult(outcome *notation.VerificationOutcome, expectedResult notation
|
||||||
t.Fatalf("assertion failed. expected : %v got : %v", expectedErr, outcome.Error)
|
t.Fatalf("assertion failed. expected : %v got : %v", expectedErr, outcome.Error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// testTrustStore implements [truststore.X509TrustStore] and returns the trusted certificates for a given trust-store.
|
|
||||||
type testTrustStore struct{}
|
|
||||||
|
|
||||||
func (ts *testTrustStore) GetCertificates(_ context.Context, _ truststore.Type, _ string) ([]*x509.Certificate, error) {
|
|
||||||
block, _ := pem.Decode([]byte(trustedCert))
|
|
||||||
cert, _ := x509.ParseCertificate(block.Bytes)
|
|
||||||
return []*x509.Certificate{cert}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func getTestDescGenFunc(returnErr bool, customDigest digest.Digest) notation.BlobDescriptorGenerator {
|
|
||||||
return func(digest.Algorithm) (ocispec.Descriptor, error) {
|
|
||||||
var err error = nil
|
|
||||||
if returnErr {
|
|
||||||
err = errors.New("intentional test desc generation error")
|
|
||||||
}
|
|
||||||
|
|
||||||
var expDigest digest.Digest = "sha384:b8ab24dafba5cf7e4c89c562f811cf10493d4203da982d3b1345f366ca863d9c2ed323dbd0fb7ff83a80302ceffa5a61"
|
|
||||||
if customDigest != "" {
|
|
||||||
expDigest = customDigest
|
|
||||||
}
|
|
||||||
|
|
||||||
return ocispec.Descriptor{
|
|
||||||
MediaType: "video/mp4",
|
|
||||||
Digest: expDigest,
|
|
||||||
Size: 12,
|
|
||||||
}, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
Loading…
Reference in New Issue