Compare commits

...

53 Commits

Author SHA1 Message Date
Junjie Gao 6063ebe30f
chore: update maintainer list: Junjie Gao retired (#551)
Signed-off-by: Junjie Gao <junjiegao@microsoft.com>
2025-06-30 17:46:34 +08:00
dependabot[bot] f457e85917
build(deps): bump golang.org/x/mod from 0.24.0 to 0.25.0 (#550) 2025-06-20 07:15:57 +00:00
dependabot[bot] 8c17f1cfc2
build(deps): bump golang.org/x/crypto from 0.38.0 to 0.39.0 (#549) 2025-06-16 07:22:29 +00:00
dependabot[bot] 2bc67e7695
build(deps): bump oras.land/oras-go/v2 from 2.5.0 to 2.6.0 (#548) 2025-05-12 01:58:18 +00:00
dependabot[bot] 9d3ac1c22d
build(deps): bump golang.org/x/crypto from 0.37.0 to 0.38.0 (#547) 2025-05-12 01:55:26 +00:00
Jakub Jarosz e1a37eb756
chore: show openssf scorecard (#543)
Signed-off-by: Jakub Jarosz <jakub@jarosz.dev>
2025-05-09 18:01:53 +08:00
Junjie Gao 626ac1d9c0
fix: remove generate-envelope plugin support for blob signing (#546)
resolves #544

---------

Signed-off-by: Junjie Gao <junjiegao@microsoft.com>
2025-05-09 16:27:41 +08:00
Patrick Zheng de3655adc0
feat: `artifactType` support in signature manifest (#542)
Signed-off-by: Patrick Zheng <patrickzheng@microsoft.com>
2025-05-08 17:01:58 +08:00
dependabot[bot] 02cc632b8b
build(deps): bump github.com/go-ldap/ldap/v3 from 3.4.10 to 3.4.11 (#539) 2025-04-28 08:58:14 +00:00
Patrick Zheng 287b8785c6
bump: bump up dependencies (#535)
Signed-off-by: Patrick Zheng <patrickzheng@microsoft.com>
2025-04-18 10:53:59 +08:00
Jakub Jarosz 81056bd695
fix: staticcheck complains - replace deprecated functions (#533)
This PR fixes a part of `staticcheck` complains reported in
https://github.com/notaryproject/notation-go/issues/531

The remaining issues will be addressed in a separate PRs.

Signed-off-by: Jakub Jarosz <jakub@jarosz.dev>
2025-04-16 13:31:47 +08:00
dependabot[bot] 0caf40c9ae
build(deps): bump golang.org/x/crypto from 0.36.0 to 0.37.0 (#530)
Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from
0.36.0 to 0.37.0.
<details>
<summary>Commits</summary>
<ul>
<li><a
href="959f8f3db0"><code>959f8f3</code></a>
go.mod: update golang.org/x dependencies</li>
<li><a
href="769bcd6997"><code>769bcd6</code></a>
ssh: use the configured rand in kex init</li>
<li><a
href="d0a798f774"><code>d0a798f</code></a>
cryptobyte: fix typo 'octects' into 'octets' for asn1.go</li>
<li><a
href="acbcbef23f"><code>acbcbef</code></a>
acme: remove unnecessary []byte conversion</li>
<li><a
href="376eb14006"><code>376eb14</code></a>
x509roots: support constrained roots</li>
<li><a
href="b369b723c8"><code>b369b72</code></a>
crypto/internal/poly1305: implement function update in assembly on
loong64</li>
<li><a
href="6b853fbea3"><code>6b853fb</code></a>
ssh/knownhosts: check more than one key</li>
<li>See full diff in <a
href="https://github.com/golang/crypto/compare/v0.36.0...v0.37.0">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=golang.org/x/crypto&package-manager=go_modules&previous-version=0.36.0&new-version=0.37.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-04-16 09:57:16 +08:00
Jakub Jarosz dbbeca1679
refactor: update error names to align with Go convention (#529)
This PR fixes https://github.com/notaryproject/notation-go/issues/528
and adds the following changes:

- add new custom errors (naming aligned with [Go
convention](https://go.dev/doc/effective_go#errors))
- mark old custom error deprecated

---------

Signed-off-by: Jakub Jarosz <jakub@jarosz.dev>
2025-04-11 15:24:06 +08:00
dependabot[bot] 3bd0ac92b2
build(deps): bump github.com/opencontainers/image-spec from 1.1.0 to 1.1.1 (#523) 2025-03-25 09:39:58 +00:00
dependabot[bot] 02ff112615
build(deps): bump golang.org/x/crypto from 0.35.0 to 0.36.0 (#525) 2025-03-25 09:36:18 +00:00
dependabot[bot] d6e291d617
build(deps): bump golang.org/x/mod from 0.23.0 to 0.24.0 (#524) 2025-03-25 09:12:14 +00:00
dependabot[bot] fd2a749d85
build(deps): bump github.com/golang-jwt/jwt/v4 from 4.5.1 to 4.5.2 (#526) 2025-03-25 09:09:36 +00:00
Patrick Zheng fcc1ce32f8
fix: update error message for signing key config (#527)
Signed-off-by: Patrick Zheng <patrickzheng@microsoft.com>
2025-03-24 14:25:55 +08:00
Junjie Gao fdcf9cc476
feat: add `SignOCI` function to return both artifact and signature manifest descriptor (#522)
Feat:
- added `SignOCI` function and **deprecated** the `Sign` function to
return both artifact and signature manifest descriptor.

Fix:
- handled referrer index deletion error as a warning to avoid confusion
about the final signing status.

Resolve part of https://github.com/notaryproject/notation/issues/695

---------

Signed-off-by: Junjie Gao <junjiegao@microsoft.com>
2025-03-10 14:03:48 +08:00
dependabot[bot] 4b058c99ef
build(deps): bump golang.org/x/crypto from 0.33.0 to 0.35.0 (#520) 2025-03-07 03:30:54 +00:00
Patrick Zheng bb2ee7a8a7
fix: timestamp against signing time (#518)
Signed-off-by: Patrick Zheng <patrickzheng@microsoft.com>
2025-02-21 16:11:11 +08:00
Junjie Gao 752832c674
bump: update go v1.23 (#512)
bump:
- updated go v1.23

Signed-off-by: Junjie Gao <junjiegao@microsoft.com>
2025-02-20 16:05:20 +08:00
dependabot[bot] 93b25a3e46
build(deps): bump golang.org/x/crypto from 0.32.0 to 0.33.0 (#510) 2025-02-17 02:30:16 +00:00
dependabot[bot] c2b5474983
build(deps): bump golang.org/x/mod from 0.22.0 to 0.23.0 (#509) 2025-02-14 01:08:20 +00:00
Patrick Zheng 6eb53a50d6
refactor: updated verifier constructor (#508)
Signed-off-by: Patrick Zheng <patrickzheng@microsoft.com>
2025-01-22 15:22:55 +08:00
Patrick Zheng 96b7133718
bump: bump up dependencies (#504)
Signed-off-by: Patrick Zheng <patrickzheng@microsoft.com>
2025-01-15 14:15:29 +08:00
dependabot[bot] 851cbabbc4
build(deps): bump golang.org/x/crypto from 0.31.0 to 0.32.0 (#503) 2025-01-14 00:44:47 +00:00
Patrick Zheng b40566d885
chore: clean up (#502)
Signed-off-by: Patrick Zheng <patrickzheng@microsoft.com>
2025-01-14 07:55:12 +08:00
Patrick Zheng 26ce0894a6
docs: fix docs (#498)
Signed-off-by: Patrick Zheng <patrickzheng@microsoft.com>
2025-01-07 08:36:20 +08:00
dependabot[bot] e5eef5e899
build(deps): bump github.com/go-ldap/ldap/v3 from 3.4.8 to 3.4.10 (#501) 2024-12-31 00:28:04 +00:00
Junjie Gao 9fe530b5fd
fix: `check-line-endings` command of Makefile (#499)
Fix:
- update the command to search script file in `.` instead of `scripts/`
folder to avoid returning an error.

---------

Signed-off-by: Junjie Gao <junjiegao@microsoft.com>
Signed-off-by: Patrick Zheng <patrickzheng@microsoft.com>
Co-authored-by: Patrick Zheng <patrickzheng@microsoft.com>
2024-12-30 10:13:10 +08:00
Patrick Zheng 3eeef95a40
chore(verifier): improve log (#497)
This PR improves log readability of the library.

Signed-off-by: Patrick Zheng <patrickzheng@microsoft.com>
2024-12-29 00:56:17 -08:00
Junjie Gao cefd007065
fix: limit the plugin output size (#484)
Fix:
- set the plugin output limit for STDOUT and STDERR to be 10MiB

Test:
- when the plugin output size exceeds 64MiB, the output pipe breaks, and
the plugin process outputs an error to STDERR

Spec changes: https://github.com/notaryproject/specifications/pull/320
Resolves #187

Signed-off-by: Junjie Gao <junjiegao@microsoft.com>
2024-12-13 08:19:49 +08:00
dependabot[bot] d1d64e7041
build(deps): bump golang.org/x/crypto from 0.29.0 to 0.31.0 (#491) 2024-12-13 00:15:40 +00:00
Patrick Zheng 57c5e0dadf
bump: bump up dependencies (#488)
Signed-off-by: Patrick Zheng <patrickzheng@microsoft.com>
2024-12-10 07:57:44 +08:00
Junjie Gao 95bac00829
perf(log): encode objects only when logged (#481)
Fix:
- replaced `.String()` with the `%v` format to avoid rendering the
string before actually logging it.

Resolves #480

Signed-off-by: Junjie Gao <junjiegao@microsoft.com>
2024-12-02 10:03:54 +08:00
Patrick Zheng e99be1954a
fix: enable timestamping cert chain revocation check during signing (#482)
Signed-off-by: Patrick Zheng <patrickzheng@microsoft.com>
2024-12-02 08:30:56 +08:00
Junjie Gao 240181a5eb
fix: add warning message for non-revokable certificate (#479)
Fix:
- added warning message for non-revokable certificate

---------

Signed-off-by: Junjie Gao <junjiegao@microsoft.com>
2024-11-29 08:38:08 +08:00
Patrick Zheng 5a323330d0
fix: timestamping (#478)
Signed-off-by: Patrick Zheng <patrickzheng@microsoft.com>
Co-authored-by: Pritesh Bandi <priteshbandi@gmail.com>
2024-11-15 16:09:00 +08:00
Pritesh Bandi 7b9636e239
chore: Improve error message in case of plugin timeout (#472)
When a plugin exceeds the specified timeout or deadline for content
processing, the current error message displayed is ```signal: killed```.
This PR updates the error message to a more informative message:
```[plugin_name] [command_name] command execution timeout: signal:
killed```

---------

Signed-off-by: Pritesh Bandi <priteshbandi@gmail.com>
2024-11-14 11:06:45 -08:00
dependabot[bot] 8797d86735
build(deps): bump golang.org/x/mod from 0.21.0 to 0.22.0 (#477) 2024-11-14 19:03:25 +00:00
Patrick Zheng cf70e77099
fix: crl cache log and err msg (#475)
Signed-off-by: Patrick Zheng <patrickzheng@microsoft.com>
2024-11-12 07:51:07 +08:00
dependabot[bot] 0191e75373
build(deps): bump golang.org/x/crypto from 0.28.0 to 0.29.0 (#476) 2024-11-11 17:44:50 +00:00
dependabot[bot] f332ed9212
build(deps): bump github.com/golang-jwt/jwt/v4 from 4.5.0 to 4.5.1 (#474) 2024-11-11 17:42:01 +00:00
dependabot[bot] f855f25526
build(deps): bump golang.org/x/crypto from 0.27.0 to 0.28.0 (#468) 2024-11-05 04:44:48 +00:00
Patrick Zheng c6c8cc7f66
chore: add crl cache debug logs (#473)
Signed-off-by: Patrick Zheng <patrickzheng@microsoft.com>
2024-11-03 20:19:01 +08:00
Patrick Zheng 1dc55d0add
fix: added tsa trust store root cert validation (#471)
This PR adds tsa trust store root cert validation while getting
certificates from trust store. This is to fail fast if cert in TSA trust
store is not a root CA certificate.

Resolves #470

---------

Signed-off-by: Patrick Zheng <patrickzheng@microsoft.com>
2024-11-01 10:40:07 -07:00
Shiwei Zhang e1b80e2f2e
Merge commit from fork
fix: OS error when setting CRL cache leads to denial of signature verification
2024-11-01 10:35:22 +08:00
Junjie Gao 7d11fa2346
fix: OS error when setting CRL cache leads to denial of signature verification
Signed-off-by: Junjie Gao <junjiegao@microsoft.com>
2024-10-23 02:59:09 +00:00
Patrick Zheng 82014a953f
chore: update logs (#469)
This PR updates logs.
Resolves #430. Also should resolve issue https://github.com/notaryproject/notation/issues/1004.

Signed-off-by: Patrick Zheng <patrickzheng@microsoft.com>
2024-10-16 10:31:10 -07:00
dependabot[bot] 11866a54a0
build(deps): bump github.com/notaryproject/notation-core-go from 1.1.1-0.20240920045731-0786f51de737 to 1.2.0-rc.1 (#466) 2024-10-01 05:51:37 +00:00
dependabot[bot] 4c2d986035
build(deps): bump github.com/veraison/go-cose from 1.1.0 to 1.3.0 (#467) 2024-10-01 05:48:38 +00:00
AdamKorcz a86f8da6ea
test: add fuzz test (#459)
Adds a fuzz test from cncf-fuzzing:
https://github.com/cncf/cncf-fuzzing/blob/main/projects/notary/fuzz_pkix_test.go

Signed-off-by: Adam Korczynski <adam@adalogics.com>
2024-09-29 14:11:20 -07:00
54 changed files with 1283 additions and 569 deletions

View File

@ -16,3 +16,6 @@ coverage:
project: project:
default: default:
target: 80% target: 80%
patch:
default:
target: 80%

View File

@ -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 @JeyJeyGao @niazfk @priteshbandi @rgnote @shizhMSFT @toddysm @Two-Hearts @vaninrao10 @yizha1 * @gokarnm @niazfk @priteshbandi @rgnote @shizhMSFT @toddysm @Two-Hearts @vaninrao10 @yizha1

View File

@ -9,7 +9,6 @@ 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)
@ -17,3 +16,6 @@ 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)

View File

@ -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 scripts -name "*.sh" -type f -exec file "{}" ";" | grep CRLF ! find . -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

View File

@ -3,6 +3,7 @@
[![Build Status](https://github.com/notaryproject/notation-go/actions/workflows/build.yml/badge.svg?event=push&branch=main)](https://github.com/notaryproject/notation-go/actions/workflows/build.yml?query=workflow%3Abuild+event%3Apush+branch%3Amain) [![Build Status](https://github.com/notaryproject/notation-go/actions/workflows/build.yml/badge.svg?event=push&branch=main)](https://github.com/notaryproject/notation-go/actions/workflows/build.yml?query=workflow%3Abuild+event%3Apush+branch%3Amain)
[![Codecov](https://codecov.io/gh/notaryproject/notation-go/branch/main/graph/badge.svg)](https://codecov.io/gh/notaryproject/notation-go) [![Codecov](https://codecov.io/gh/notaryproject/notation-go/branch/main/graph/badge.svg)](https://codecov.io/gh/notaryproject/notation-go)
[![Go Reference](https://pkg.go.dev/badge/github.com/notaryproject/notation-go.svg)](https://pkg.go.dev/github.com/notaryproject/notation-go@main) [![Go Reference](https://pkg.go.dev/badge/github.com/notaryproject/notation-go.svg)](https://pkg.go.dev/github.com/notaryproject/notation-go@main)
[![OpenSSF Scorecard](https://api.scorecard.dev/projects/github.com/notaryproject/notation-go/badge)](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.

35
config/errors.go Normal file
View File

@ -0,0 +1,35 @@
// 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"
}

28
config/errors_test.go Normal file
View File

@ -0,0 +1,28 @@
// 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")
}
}

View File

@ -50,9 +50,6 @@ 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"`
@ -67,13 +64,12 @@ 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 errorKeyNameEmpty return ErrKeyNameEmpty
} }
_, 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{
@ -88,25 +84,20 @@ 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 errorKeyNameEmpty return ErrKeyNameEmpty
} }
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{
@ -115,7 +106,6 @@ 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
@ -127,14 +117,12 @@ 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{}, errorKeyNameEmpty return KeySuite{}, ErrKeyNameEmpty
} }
idx := slices.IndexIsser(s.Keys, keyName) idx := slices.IndexIsser(s.Keys, keyName)
if idx < 0 { if idx < 0 {
return KeySuite{}, errKeyNotFound return KeySuite{}, KeyNotFoundError{KeyName: keyName}
} }
return s.Keys[idx], nil return s.Keys[idx], nil
} }
@ -144,7 +132,6 @@ 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)
} }
@ -153,12 +140,11 @@ 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, errorKeyNameEmpty return deletedNames, ErrKeyNameEmpty
} }
idx := slices.IndexIsser(s.Keys, name) idx := slices.IndexIsser(s.Keys, name)
if idx < 0 { if idx < 0 {
return deletedNames, errors.New(name + ": not found") return deletedNames, KeyNotFoundError{KeyName: name}
} }
s.Keys = slices.Delete(s.Keys, idx) s.Keys = slices.Delete(s.Keys, idx)
deletedNames = append(deletedNames, name) deletedNames = append(deletedNames, name)
@ -172,13 +158,11 @@ 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 errorKeyNameEmpty return ErrKeyNameEmpty
} }
if !slices.ContainsIsser(s.Keys, keyName) { if !slices.ContainsIsser(s.Keys, keyName) {
return fmt.Errorf("key with name '%s' not found", keyName) return KeyNotFoundError{KeyName: keyName}
} }
s.Default = &keyName s.Default = &keyName
return nil return nil
} }
@ -189,11 +173,9 @@ 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)
} }
@ -208,11 +190,9 @@ 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
} }
@ -224,11 +204,9 @@ 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()
} }
@ -241,12 +219,10 @@ 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
} }
@ -262,17 +238,14 @@ 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
} }

View File

@ -17,6 +17,7 @@ import (
"context" "context"
"crypto/x509" "crypto/x509"
"encoding/pem" "encoding/pem"
"errors"
"os" "os"
"path/filepath" "path/filepath"
"reflect" "reflect"
@ -310,14 +311,22 @@ func TestGet(t *testing.T) {
}) })
t.Run("NonExistent", func(t *testing.T) { t.Run("NonExistent", func(t *testing.T) {
if _, err := sampleSigningKeysInfo.Get("nonExistent"); err == nil { _, err := sampleSigningKeysInfo.Get("nonExistent")
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("InvalidName", func(t *testing.T) { t.Run("EmptyName", func(t *testing.T) {
if _, err := sampleSigningKeysInfo.Get(""); err == nil { _, err := sampleSigningKeysInfo.Get("")
t.Error("expected Get() to fail for invalid key name") if err == nil {
t.Error("expected Get() to fail for empty key name")
}
if !errors.Is(err, ErrKeyNameEmpty) {
t.Error("expected Get() to return ErrorKeyNameEmpty")
} }
}) })
} }
@ -358,14 +367,22 @@ func TestUpdateDefault(t *testing.T) {
}) })
t.Run("NonExistent", func(t *testing.T) { t.Run("NonExistent", func(t *testing.T) {
if err := sampleSigningKeysInfo.UpdateDefault("nonExistent"); err == nil { err := sampleSigningKeysInfo.UpdateDefault("nonExistent")
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("InvalidName", func(t *testing.T) { t.Run("EmptyName", func(t *testing.T) {
if err := sampleSigningKeysInfo.UpdateDefault(""); err == nil { err := sampleSigningKeysInfo.UpdateDefault("")
t.Error("expected Get() to fail for invalid key name") if err == nil {
t.Error("expected Get() to fail for empty key name")
}
if !errors.Is(err, ErrKeyNameEmpty) {
t.Error("expected Get() to return ErrorKeyNameEmpty")
} }
}) })
} }
@ -382,21 +399,28 @@ 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) {
if _, err := testSigningKeysInfo.Remove(testKeyName); err == nil { _, err := testSigningKeysInfo.Remove("nonExistent")
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("InvalidName", func(t *testing.T) { t.Run("EmptyName", func(t *testing.T) {
if _, err := testSigningKeysInfo.Remove(""); err == nil { _, err := testSigningKeysInfo.Remove("")
t.Error("expected Get() to fail for invalid key name") if err == nil {
t.Error("expected Get() to fail for empty key name")
}
if !errors.Is(err, ErrKeyNameEmpty) {
t.Error("expected Get() to return ErrorKeyNameEmpty")
} }
}) })
} }

View File

@ -59,6 +59,7 @@ const (
// 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 OCI trust policy file relative path.
//
// Deprecated: PathTrustPolicy exists for historical compatibility and should not be used. // Deprecated: PathTrustPolicy exists for historical compatibility and should not be used.
// To get OCI trust policy path, use PathOCITrustPolicy. // To get OCI trust policy path, use PathOCITrustPolicy.
PathTrustPolicy = "trustpolicy.json" PathTrustPolicy = "trustpolicy.json"

View File

@ -15,11 +15,17 @@ 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 ErrorPushSignatureFailed) Error() string { func (e PushSignatureFailedError) 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
} }
@ -28,11 +34,17 @@ func (e ErrorPushSignatureFailed) 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 ErrorVerificationInconclusive) Error() string { func (e VerificationInconclusiveError) Error() string {
if e.Msg != "" { if e.Msg != "" {
return e.Msg return e.Msg
} }
@ -41,11 +53,17 @@ func (e ErrorVerificationInconclusive) 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 ErrorNoApplicableTrustPolicy) Error() string { func (e NoApplicableTrustPolicyError) Error() string {
if e.Msg != "" { if e.Msg != "" {
return e.Msg return e.Msg
} }
@ -54,11 +72,17 @@ func (e ErrorNoApplicableTrustPolicy) 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 ErrorSignatureRetrievalFailed) Error() string { func (e SignatureRetrievalFailedError) Error() string {
if e.Msg != "" { if e.Msg != "" {
return e.Msg return e.Msg
} }
@ -67,11 +91,17 @@ func (e ErrorSignatureRetrievalFailed) 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 ErrorVerificationFailed) Error() string { func (e VerificationFailedError) Error() string {
if e.Msg != "" { if e.Msg != "" {
return e.Msg return e.Msg
} }
@ -80,11 +110,17 @@ func (e ErrorVerificationFailed) 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 ErrorUserMetadataVerificationFailed) Error() string { func (e UserMetadataVerificationFailedError) Error() string {
if e.Msg != "" { if e.Msg != "" {
return e.Msg return e.Msg
} }

View File

@ -91,3 +91,80 @@ 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)
}
})
}
}

View File

@ -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 certificate requirements: // chain, following the Notary Project 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 {

View File

@ -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.Document{ var examplePolicyDocument = trustpolicy.OCIDocument{
Version: "1.0", Version: "1.0",
TrustPolicies: []trustpolicy.TrustPolicy{ TrustPolicies: []trustpolicy.OCITrustPolicy{
{ {
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 and add trusted // Users could use the default trust store from Notary Project and
// certificates into it following the trust store spec: // add trusted 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 certificate requirements: // Notary Project 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

View File

@ -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 certificate requirements: // chain, following the Notary Project 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,13 +70,16 @@ 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.
targetDesc, err := notation.Sign(context.Background(), exampleSigner, exampleRepo, exampleSignOptions) targetManifestDesc, sigManifestDesc, err := notation.SignOCI(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("targetDesc MediaType:", targetDesc.MediaType) fmt.Println("targetManifestDesc.MediaType:", targetManifestDesc.MediaType)
fmt.Println("targetDesc Digest:", targetDesc.Digest) fmt.Println("targetManifestDesc.Digest:", targetManifestDesc.Digest)
fmt.Println("targetDesc Size:", targetDesc.Size) fmt.Println("targetManifestDesc.Size:", targetManifestDesc.Size)
fmt.Println("sigManifestDesc.MediaType:", sigManifestDesc.MediaType)
fmt.Println("sigManifestDesc.Digest:", sigManifestDesc.Digest)
fmt.Println("sigManifestDesc.Size:", sigManifestDesc.Size)
} }

View File

@ -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.Document{ examplePolicyDocument := trustpolicy.OCIDocument{
Version: "1.0", Version: "1.0",
TrustPolicies: []trustpolicy.TrustPolicy{ TrustPolicies: []trustpolicy.OCITrustPolicy{
{ {
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 certificate requirements: // Notary Project 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

View File

@ -24,14 +24,16 @@ import (
"github.com/notaryproject/notation-go/signer" "github.com/notaryproject/notation-go/signer"
) )
// ExampleSignBlob demonstrates how to use signer.BlobSign to sign arbitrary data. // ExampleSignBlob demonstrates how to use [notation.SignBlob] to sign arbitrary
// data.
func Example_signBlob() { func Example_signBlob() {
//exampleSigner implements notation.Signer and notation.BlobSigner. Given key and X509 certificate chain, // exampleSigner implements [notation.Signer] and [notation.BlobSigner].
// it provides method to sign OCI artifacts or blobs. // Given key and X509 certificate chain, it provides method to sign OCI
// artifacts or blobs.
// 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 certificate chain, // key and replace `exampleCerts` with the corresponding certificate chain,
//following the Notary certificate requirements: // following the Notary Project certificate requirements:
// https://github.com/notaryproject/notaryproject/blob/v1.0.0/specs/signature-specification.md#certificate-requirements // https://github.com/notaryproject/specifications/tree/9c81dc773508dedc5a81c02c8d805de04f65050b/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 {
panic(err) // Handle error panic(err) // Handle error
@ -42,7 +44,7 @@ func Example_signBlob() {
exampleSignatureMediaType := jws.MediaTypeEnvelope exampleSignatureMediaType := jws.MediaTypeEnvelope
exampleContentMediaType := "video/mp4" exampleContentMediaType := "video/mp4"
// exampleSignOptions is an example of notation.SignBlobOptions. // exampleSignOptions is an example of [notation.SignBlobOptions].
exampleSignOptions := notation.SignBlobOptions{ exampleSignOptions := notation.SignBlobOptions{
SignerSignOptions: notation.SignerSignOptions{ SignerSignOptions: notation.SignerSignOptions{
SignatureMediaType: exampleSignatureMediaType, SignatureMediaType: exampleSignatureMediaType,
@ -52,7 +54,8 @@ func Example_signBlob() {
UserMetadata: map[string]string{"buildId": "101"}, 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 reads the data that needs to be signed.
// This data can be in a file or in memory.
exampleReader := strings.NewReader("example blob") exampleReader := strings.NewReader("example blob")
// Upon successful signing, signature envelope and signerInfo are returned. // Upon successful signing, signature envelope and signerInfo are returned.

View File

@ -21,6 +21,8 @@ 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"
@ -43,7 +45,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 certificate requirements: // chain, following the Notary Project 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 {
@ -77,23 +79,35 @@ 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,
} }
targetDesc, err := notation.Sign(context.Background(), exampleSigner, exampleRepo, exampleSignOptions) targetManifestDesc, sigManifestDesc, err := notation.SignOCI(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("targetDesc MediaType:", targetDesc.MediaType) fmt.Println("targetManifestDesc.MediaType:", targetManifestDesc.MediaType)
fmt.Println("targetDesc Digest:", targetDesc.Digest) fmt.Println("targetManifestDesc.Digest:", targetManifestDesc.Digest)
fmt.Println("targetDesc Size:", targetDesc.Size) fmt.Println("targetManifestDesc.Size:", targetManifestDesc.Size)
fmt.Println("sigManifestDesc.MediaType:", sigManifestDesc.MediaType)
fmt.Println("sigManifestDesc.Digest:", sigManifestDesc.Digest)
fmt.Println("sigManifestDesc.Size:", sigManifestDesc.Size)
} }

View File

@ -27,9 +27,9 @@ import (
"github.com/notaryproject/notation-go/verifier/truststore" "github.com/notaryproject/notation-go/verifier/truststore"
) )
// examplePolicyDocument is an example of a valid trust policy document. // exampleBlobPolicyDocument is an example of a valid blob trust policy document.
// trust policy document should follow this spec: // blob trust policy document should follow this spec:
// https://github.com/notaryproject/notaryproject/blob/v1.1.0/specs/trust-store-trust-policy.md#trust-policy // https://github.com/notaryproject/specifications/tree/9c81dc773508dedc5a81c02c8d805de04f65050b/specs/trust-store-trust-policy.md#blob-trust-policy
var exampleBlobPolicyDocument = trustpolicy.BlobDocument{ var exampleBlobPolicyDocument = trustpolicy.BlobDocument{
Version: "1.0", Version: "1.0",
TrustPolicies: []trustpolicy.BlobTrustPolicy{ TrustPolicies: []trustpolicy.BlobTrustPolicy{
@ -42,8 +42,8 @@ var exampleBlobPolicyDocument = trustpolicy.BlobDocument{
}, },
} }
// ExampleVerifyBlob demonstrates how to use verifier.Verify to verify a // ExampleVerifyBlob demonstrates how to use [notation.VerifyBlob] to verify a
// signature of the blob. // signature of an arbitrary blob.
func Example_verifyBlob() { func Example_verifyBlob() {
// Both COSE ("application/cose") and JWS ("application/jose+json") // Both COSE ("application/cose") and JWS ("application/jose+json")
// signature mediaTypes are supported. // signature mediaTypes are supported.
@ -53,23 +53,26 @@ func Example_verifyBlob() {
exampleSignatureEnvelope := getSignatureEnvelope() exampleSignatureEnvelope := getSignatureEnvelope()
// createTrustStoreForBlobVerify creates a trust store directory for demo purpose. // createTrustStoreForBlobVerify creates a trust store directory for demo purpose.
// Users could use the default trust store from Notary and add trusted // Users could use the default trust store from Notary Project and 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/specifications/tree/9c81dc773508dedc5a81c02c8d805de04f65050b/specs/trust-store-trust-policy.md#trust-store
if err := createTrustStoreForBlobVerify(); err != nil { if err := createTrustStoreForBlobVerify(); err != nil {
panic(err) // Handle error panic(err) // Handle error
} }
// exampleVerifier implements notation.Verify and notation.VerifyBlob. // exampleVerifier implements [notation.Verify] and [notation.VerifyBlob].
exampleVerifier, err := verifier.NewVerifier(nil, &exampleBlobPolicyDocument, truststore.NewX509TrustStore(dir.ConfigFS()), nil) exampleVerifier, err := verifier.NewVerifierWithOptions(truststore.NewX509TrustStore(dir.ConfigFS()), verifier.VerifierOptions{
BlobTrustPolicy: &exampleBlobPolicyDocument,
})
if err != nil { if err != nil {
panic(err) // Handle error panic(err) // Handle error
} }
// exampleReader reads the data that needs to be verified. This data can be in a file or in memory. // exampleReader reads the data that needs to be verified.
// This data can be in a file or in memory.
exampleReader := strings.NewReader("example blob") exampleReader := strings.NewReader("example blob")
// exampleVerifyOptions is an example of notation.VerifierVerifyOptions // exampleVerifyOptions is an example of [notation.VerifyBlobOptions]
exampleVerifyOptions := notation.VerifyBlobOptions{ exampleVerifyOptions := notation.VerifyBlobOptions{
BlobVerifierVerifyOptions: notation.BlobVerifierVerifyOptions{ BlobVerifierVerifyOptions: notation.BlobVerifierVerifyOptions{
SignatureMediaType: exampleSignatureMediaType, SignatureMediaType: exampleSignatureMediaType,
@ -110,8 +113,8 @@ func createTrustStoreForBlobVerify() 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 certificate requirements: // Notary Project certificate requirements:
// https://github.com/notaryproject/notaryproject/blob/v1.0.0/specs/signature-specification.md#certificate-requirements // https://github.com/notaryproject/specifications/tree/9c81dc773508dedc5a81c02c8d805de04f65050b/specs/signature-specification.md#certificate-requirements
exampleX509Certificate := `-----BEGIN CERTIFICATE----- exampleX509Certificate := `-----BEGIN CERTIFICATE-----
MIIEbDCCAtSgAwIBAgIBUzANBgkqhkiG9w0BAQsFADBkMQswCQYDVQQGEwJVUzEL MIIEbDCCAtSgAwIBAgIBUzANBgkqhkiG9w0BAQsFADBkMQswCQYDVQQGEwJVUzEL
MAkGA1UECBMCV0ExEDAOBgNVBAcTB1NlYXR0bGUxDzANBgNVBAoTBk5vdGFyeTEl MAkGA1UECBMCV0ExEDAOBgNVBAcTB1NlYXR0bGUxDzANBgNVBAoTBk5vdGFyeTEl

View File

@ -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.Document{ examplePolicyDocument := trustpolicy.OCIDocument{
Version: "1.0", Version: "1.0",
TrustPolicies: []trustpolicy.TrustPolicy{ TrustPolicies: []trustpolicy.OCITrustPolicy{
{ {
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 certificate requirements: // Notary Project 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 certificate requirements: // Notary Project 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
View File

@ -1,26 +1,26 @@
module github.com/notaryproject/notation-go module github.com/notaryproject/notation-go
go 1.22.0 go 1.23.0
require ( require (
github.com/go-ldap/ldap/v3 v3.4.8 github.com/go-ldap/ldap/v3 v3.4.11
github.com/notaryproject/notation-core-go v1.1.1-0.20240920045731-0786f51de737 github.com/notaryproject/notation-core-go v1.3.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 v0.2.0 github.com/notaryproject/tspclient-go v1.0.0
github.com/opencontainers/go-digest v1.0.0 github.com/opencontainers/go-digest v1.0.0
github.com/opencontainers/image-spec v1.1.0 github.com/opencontainers/image-spec v1.1.1
github.com/veraison/go-cose v1.1.0 github.com/veraison/go-cose v1.3.0
golang.org/x/crypto v0.27.0 golang.org/x/crypto v0.39.0
golang.org/x/mod v0.21.0 golang.org/x/mod v0.25.0
oras.land/oras-go/v2 v2.5.0 oras.land/oras-go/v2 v2.6.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.7.0 // indirect github.com/fxamacker/cbor/v2 v2.8.0 // indirect
github.com/go-asn1-ber/asn1-ber v1.5.5 // indirect github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667 // indirect
github.com/golang-jwt/jwt/v4 v4.5.0 // indirect github.com/golang-jwt/jwt/v4 v4.5.2 // 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.6.0 // indirect golang.org/x/sync v0.14.0 // indirect
) )

110
go.sum
View File

@ -2,22 +2,18 @@ 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.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E= github.com/fxamacker/cbor/v2 v2.8.0 h1:fFtUGXUzXPHTIUdne5+zzMPTfffl3RD5qYnkY40vtxU=
github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= github.com/fxamacker/cbor/v2 v2.8.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ=
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 h1:BP4M0CvQ4S3TGls2FvczZtj5Re/2ZzkV9VwqPHH/3Bo=
github.com/go-asn1-ber/asn1-ber v1.5.5/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0= github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0=
github.com/go-ldap/ldap/v3 v3.4.8 h1:loKJyspcRezt2Q3ZRMq2p/0v8iOurlmeXDPw6fikSvQ= github.com/go-ldap/ldap/v3 v3.4.11 h1:4k0Yxweg+a3OyBLjdYn5OKglv18JNvfDykSoI8bW0gU=
github.com/go-ldap/ldap/v3 v3.4.8/go.mod h1:qS3Sjlu76eHfHGpUdWkAXQTw4beih+cHsco2jXlIXrk= github.com/go-ldap/ldap/v3 v3.4.11/go.mod h1:bY7t0FLK8OAVpp/vV6sSlpz3EQDGcQwc8pF0ujLgKvM=
github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg= github.com/golang-jwt/jwt/v4 v4.5.2 h1:YtQM7lnr8iZ+j5q71MGKkNw9Mn7AjHM68uc9g5fXeUI=
github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= github.com/golang-jwt/jwt/v4 v4.5.2/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=
@ -32,87 +28,33 @@ 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.1.1-0.20240920045731-0786f51de737 h1:Hp93KBCABE29+6zdS0GTg0T1SXj6qGatJyN1JMvTQqk= github.com/notaryproject/notation-core-go v1.3.0 h1:mWJaw1QBpBxpjLSiKOjzbZvB+xh2Abzk14FHWQ+9Kfs=
github.com/notaryproject/notation-core-go v1.1.1-0.20240920045731-0786f51de737/go.mod h1:b/70rA4OgOHlg0A7pb8zTWKJadFO6781zS3a37KHEJQ= github.com/notaryproject/notation-core-go v1.3.0/go.mod h1:hzvEOit5lXfNATGNBT8UQRx2J6Fiw/dq/78TQL8aE64=
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 v0.2.0 h1:g/KpQGmyk/h7j60irIRG1mfWnibNOzJ8WhLqAzuiQAQ= github.com/notaryproject/tspclient-go v1.0.0 h1:AwQ4x0gX8IHnyiZB1tggpn5NFqHpTEm1SDX8YNv4Dg4=
github.com/notaryproject/tspclient-go v0.2.0/go.mod h1:LGyA/6Kwd2FlM0uk8Vc5il3j0CddbWSHBj/4kxQDbjs= github.com/notaryproject/tspclient-go v1.0.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.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug= github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040=
github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM= github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M=
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.1.0 h1:AalPS4VGiKavpAzIlBjrn7bhqXiXi4jbMYY/2+UC+4o= github.com/veraison/go-cose v1.3.0 h1:2/H5w8kdSpQJyVtIhx8gmwPJ2uSz1PkyWFx0idbd7rk=
github.com/veraison/go-cose v1.1.0/go.mod h1:7ziE85vSq4ScFTg6wyoMXjucIGOf4JkFEZi/an96Ct4= github.com/veraison/go-cose v1.3.0/go.mod h1:df09OV91aHoQWLmy1KsDdYiagtXgyAwAl8vFeFn1gMc=
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=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= golang.org/x/crypto v0.39.0 h1:SHs+kF4LP+f+p14esP5jAoDpHU8Gu/v9lFRK6IT5imM=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/mod v0.25.0 h1:n7a+ZbQKQA/Ysbyb0/6IbB1H/X41mKgbhfv7AfG/44w=
golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= golang.org/x/mod v0.25.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8=
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A= golang.org/x/sync v0.14.0 h1:woo0S4Yywslg6hp4eUFjTVOyKt0RookbpAHG4c1HmhQ=
golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70= golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
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.21.0 h1:vvrHzRwRfVKSiLrG+d4FMl/Qi4ukBCE6kZlTUkDYRT0=
golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
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.5.0 h1:o8Me9kLY74Vp5uw07QXPiitjsw7qNXi8Twd+19Zf02c= oras.land/oras-go/v2 v2.6.0 h1:X4ELRsiGkrbeox69+9tzTu492FMUu7zJQW6eJU+I2oc=
oras.land/oras-go/v2 v2.5.0/go.mod h1:z4eisnLP530vwIOUOJeBIj0aGI0L1C3d53atvCBqZHg= oras.land/oras-go/v2 v2.6.0/go.mod h1:magiQDfG6H1O9APp+rOsvCPcW1GD2MM7vgnKY0Y+u1o=

View File

@ -119,8 +119,15 @@ func TrimFileExtension(fileName string) string {
// WriteFile writes content to a temporary file and moves it to path. // WriteFile writes content to a temporary file and moves it to path.
// If path already exists and is a file, WriteFile overwrites it. // If path already exists and is a file, WriteFile overwrites it.
func WriteFile(path string, content []byte) (writeErr error) { //
tempFile, err := os.CreateTemp("", tempFileNamePrefix) // 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 { if err != nil {
return fmt.Errorf("failed to create temp file: %w", err) return fmt.Errorf("failed to create temp file: %w", err)
} }

View File

@ -30,7 +30,7 @@ func TestCopyToDir(t *testing.T) {
if err := os.MkdirAll(filepath.Dir(filename), 0700); err != nil { if err := os.MkdirAll(filepath.Dir(filename), 0700); err != nil {
t.Fatal(err) t.Fatal(err)
} }
if err := WriteFile(filename, data); err != nil { if err := WriteFile(tempDir, filename, data); err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -52,7 +52,7 @@ func TestCopyToDir(t *testing.T) {
if err := os.MkdirAll(filepath.Dir(filename), 0700); err != nil { if err := os.MkdirAll(filepath.Dir(filename), 0700); err != nil {
t.Fatal(err) t.Fatal(err)
} }
if err := WriteFile(filename, data); err != nil { if err := WriteFile(tempDir, filename, data); err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -87,7 +87,7 @@ func TestCopyToDir(t *testing.T) {
if err := os.MkdirAll(filepath.Dir(filename), 0700); err != nil { if err := os.MkdirAll(filepath.Dir(filename), 0700); err != nil {
t.Fatal(err) t.Fatal(err)
} }
if err := WriteFile(filename, data); err != nil { if err := WriteFile(tempDir, filename, data); err != nil {
t.Fatal(err) t.Fatal(err)
} }
// forbid reading // forbid reading
@ -113,7 +113,7 @@ func TestCopyToDir(t *testing.T) {
if err := os.MkdirAll(filepath.Dir(filename), 0700); err != nil { if err := os.MkdirAll(filepath.Dir(filename), 0700); err != nil {
t.Fatal(err) t.Fatal(err)
} }
if err := WriteFile(filename, data); err != nil { if err := WriteFile(tempDir, filename, data); err != nil {
t.Fatal(err) t.Fatal(err)
} }
// forbid dest directory operation // forbid dest directory operation
@ -139,7 +139,7 @@ func TestCopyToDir(t *testing.T) {
if err := os.MkdirAll(filepath.Dir(filename), 0700); err != nil { if err := os.MkdirAll(filepath.Dir(filename), 0700); err != nil {
t.Fatal(err) t.Fatal(err)
} }
if err := WriteFile(filename, data); err != nil { if err := WriteFile(tempDir, filename, data); err != nil {
t.Fatal(err) t.Fatal(err)
} }
// forbid writing to destTempDir // forbid writing to destTempDir
@ -159,7 +159,7 @@ func TestCopyToDir(t *testing.T) {
if err := os.MkdirAll(filepath.Dir(filename), 0700); err != nil { if err := os.MkdirAll(filepath.Dir(filename), 0700); err != nil {
t.Fatal(err) t.Fatal(err)
} }
if err := WriteFile(filename, data); err != nil { if err := WriteFile(tempDir, filename, data); err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -192,7 +192,7 @@ func TestWriteFile(t *testing.T) {
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
err = WriteFile(filepath.Join(tempDir, "testFile"), content) err = WriteFile(tempDir, filepath.Join(tempDir, "testFile"), content)
if err == nil || !strings.Contains(err.Error(), "permission denied") { if err == nil || !strings.Contains(err.Error(), "permission denied") {
t.Fatalf("expected permission denied error, but got %s", err) t.Fatalf("expected permission denied error, but got %s", err)
} }

View File

@ -0,0 +1,53 @@
// 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
}

View File

@ -0,0 +1,67 @@
// 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)
}
}

View File

@ -0,0 +1,24 @@
// 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)
})
}

View File

@ -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. // and Verification. It supports both OCI artifact and arbitrary blob.
package notation package notation
import ( import (
@ -31,6 +31,7 @@ import (
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"
@ -47,7 +48,7 @@ var errDoneVerification = errors.New("done verification")
var reservedAnnotationPrefixes = [...]string{"io.cncf.notary"} var reservedAnnotationPrefixes = [...]string{"io.cncf.notary"}
// SignerSignOptions contains parameters for Signer.Sign. // SignerSignOptions contains parameters for [Signer] and [BlobSigner].
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
@ -69,6 +70,11 @@ 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.
@ -80,18 +86,21 @@ 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. // SignBlobOptions contains parameters for [notation.SignBlob].
type SignBlobOptions struct { type SignBlobOptions struct {
SignerSignOptions SignerSignOptions
// ContentMediaType is the media-type of the blob being signed. // ContentMediaType is the media-type of the blob being signed.
ContentMediaType string ContentMediaType string
// UserMetadata contains key-value pairs that are added to the signature // UserMetadata contains key-value pairs that are added to the signature
// payload // payload
UserMetadata map[string]string UserMetadata map[string]string
} }
// BlobDescriptorGenerator creates descriptor using the digest Algorithm. // BlobDescriptorGenerator creates descriptor using the digest Algorithm.
// Below is the example of minimal descriptor, it must contain mediatype, digest and size of the artifact // Below is the example of minimal descriptor, it must contain mediatype,
// digest and size of the artifact.
// //
// { // {
// "mediaType": "application/octet-stream", // "mediaType": "application/octet-stream",
@ -104,8 +113,8 @@ type BlobDescriptorGenerator func(digest.Algorithm) (ocispec.Descriptor, error)
// The interface allows signing with local or remote keys, // The interface allows signing with local or remote keys,
// and packing in various signature formats. // and packing in various signature formats.
type BlobSigner interface { type BlobSigner interface {
// SignBlob signs the descriptor returned by genDesc , // SignBlob signs the descriptor returned by genDesc, and returns the
// and returns the signature and SignerInfo // signature and SignerInfo.
SignBlob(ctx context.Context, genDesc BlobDescriptorGenerator, opts SignerSignOptions) ([]byte, *signature.SignerInfo, error) SignBlob(ctx context.Context, genDesc BlobDescriptorGenerator, opts SignerSignOptions) ([]byte, *signature.SignerInfo, error)
} }
@ -116,7 +125,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
@ -131,13 +140,30 @@ 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{}, err return ocispec.Descriptor{}, ocispec.Descriptor{}, err
} }
if repo == nil { if repo == nil {
return ocispec.Descriptor{}, errors.New("repo cannot be nil") return ocispec.Descriptor{}, ocispec.Descriptor{}, errors.New("repo cannot be nil")
} }
logger := log.GetLogger(ctx) logger := log.GetLogger(ctx)
@ -146,69 +172,70 @@ func Sign(ctx context.Context, signer Signer, repo registry.Repository, signOpts
// artifactRef is a valid full reference // artifactRef is a valid full reference
artifactRef = ref.Reference artifactRef = ref.Reference
} }
targetDesc, err := repo.Resolve(ctx, artifactRef) artifactManifestDesc, err = repo.Resolve(ctx, artifactRef)
if err != nil { if err != nil {
return ocispec.Descriptor{}, fmt.Errorf("failed to resolve reference: %w", err) return ocispec.Descriptor{}, 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 != targetDesc.Digest.String() { if artifactRef != artifactManifestDesc.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{}, fmt.Errorf("user input digest %s does not match the resolved digest %s", artifactRef, targetDesc.Digest.String()) return ocispec.Descriptor{}, ocispec.Descriptor{}, fmt.Errorf("user input digest %s does not match the resolved digest %s", artifactRef, artifactManifestDesc.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 `%s` before signing", artifactRef, targetDesc.Digest.String()) logger.Infof("Resolved artifact tag `%s` to digest `%v` before signing", artifactRef, artifactManifestDesc.Digest)
} }
descToSign, err := addUserMetadataToDescriptor(ctx, targetDesc, signOpts.UserMetadata) descToSign, err := addUserMetadataToDescriptor(ctx, artifactManifestDesc, signOpts.UserMetadata)
if err != nil { if err != nil {
return ocispec.Descriptor{}, err return ocispec.Descriptor{}, 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{}, err return ocispec.Descriptor{}, 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{}, err return ocispec.Descriptor{}, 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", targetDesc, signOpts.SignatureMediaType) logger.Debugf("Pushing signature of artifact descriptor: %+v, signature media type: %v", artifactManifestDesc, signOpts.SignatureMediaType)
_, _, err = repo.PushSignature(ctx, signOpts.SignatureMediaType, sig, targetDesc, annotations) _, sigManifestDesc, err = repo.PushSignature(ctx, signOpts.SignatureMediaType, sig, artifactManifestDesc, annotations)
if err != nil { if err != nil {
var referrerError *remote.ReferrersError var referrerError *remote.ReferrersError
// do not log an error for failing to delete referral index if errors.As(err, &referrerError) && referrerError.IsReferrersIndexDelete() {
if !errors.As(err, &referrerError) || !referrerError.IsReferrersIndexDelete() { // return the descriptors for referrersIndexDelete error as
// the signature is successfully pushed to the repository
return artifactManifestDesc, sigManifestDesc, err
}
logger.Error("Failed to push the signature") logger.Error("Failed to push the signature")
return ocispec.Descriptor{}, ocispec.Descriptor{}, ErrorPushSignatureFailed{Msg: err.Error()}
} }
return ocispec.Descriptor{}, ErrorPushSignatureFailed{Msg: err.Error()} return artifactManifestDesc, sigManifestDesc, nil
} }
return targetDesc, nil // SignBlob signs the arbitrary data from blobReader and returns
} // the signature and SignerInfo.
// SignBlob signs the arbitrary data and returns the signature
func SignBlob(ctx context.Context, signer BlobSigner, blobReader io.Reader, signBlobOpts SignBlobOptions) ([]byte, *signature.SignerInfo, error) { func SignBlob(ctx context.Context, signer BlobSigner, blobReader io.Reader, signBlobOpts SignBlobOptions) ([]byte, *signature.SignerInfo, error) {
// sanity checks // sanity checks
if err := validateSignArguments(signer, signBlobOpts.SignerSignOptions); err != nil { if err := validateSignArguments(signer, signBlobOpts.SignerSignOptions); err != nil {
return nil, nil, err return nil, nil, err
} }
if blobReader == nil { if blobReader == nil {
return nil, nil, errors.New("blobReader cannot be nil") return nil, nil, errors.New("blobReader cannot be nil")
} }
if signBlobOpts.ContentMediaType == "" { if signBlobOpts.ContentMediaType == "" {
return nil, nil, errors.New("content media-type cannot be empty") return nil, nil, errors.New("content media-type cannot be empty")
} }
if err := validateContentMediaType(signBlobOpts.ContentMediaType); err != nil { if err := validateContentMediaType(signBlobOpts.ContentMediaType); err != nil {
return nil, nil, err return nil, nil, err
} }
@ -233,33 +260,26 @@ 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
} }
@ -301,6 +321,7 @@ 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")
@ -311,15 +332,14 @@ 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 verifying OCI artifact. // VerifierVerifyOptions contains parameters for [Verifier.Verify] used for
// 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.
@ -338,7 +358,7 @@ type VerifierVerifyOptions struct {
UserMetadata map[string]string UserMetadata map[string]string
} }
// Verifier is a interface for verifying an OCI artifact. // Verifier is a generic 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
@ -348,7 +368,7 @@ type Verifier interface {
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. // BlobVerifierVerifyOptions contains parameters for [BlobVerifier.Verify].
type BlobVerifierVerifyOptions struct { type BlobVerifierVerifyOptions struct {
// SignatureMediaType is the envelope type of the signature. // SignatureMediaType is the envelope type of the signature.
// Currently only `application/jose+json` and `application/cose` are // Currently only `application/jose+json` and `application/cose` are
@ -369,7 +389,7 @@ type BlobVerifierVerifyOptions struct {
// BlobVerifier is a generic interface for verifying a blob. // BlobVerifier is a generic interface for verifying a blob.
type BlobVerifier interface { type BlobVerifier interface {
// VerifyBlob verifies the `signature` against the target artifact using the // VerifyBlob verifies the `signature` against the target blob using the
// descriptor returned by descGenFunc parameter and // descriptor returned by descGenFunc parameter and
// returns the outcome upon successful verification. // returns the outcome upon successful verification.
VerifyBlob(ctx context.Context, descGenFunc BlobDescriptorGenerator, signature []byte, opts BlobVerifierVerifyOptions) (*VerificationOutcome, error) VerifyBlob(ctx context.Context, descGenFunc BlobDescriptorGenerator, signature []byte, opts BlobVerifierVerifyOptions) (*VerificationOutcome, error)
@ -380,7 +400,7 @@ type verifySkipper interface {
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.
@ -399,7 +419,7 @@ type VerifyOptions struct {
UserMetadata map[string]string UserMetadata map[string]string
} }
// VerifyBlobOptions contains parameters for notation.VerifyBlob. // VerifyBlobOptions contains parameters for [notation.VerifyBlob].
type VerifyBlobOptions struct { type VerifyBlobOptions struct {
BlobVerifierVerifyOptions BlobVerifierVerifyOptions
@ -408,32 +428,27 @@ type VerifyBlobOptions struct {
} }
// VerifyBlob performs signature verification for a blob using notation supported // VerifyBlob performs signature verification for a blob using notation supported
// verification types (like integrity, authenticity, etc.) and return the // verification types (like integrity, authenticity, etc.) and returns the
// successful signature verification outcome. The blob is read using blobReader and // successful signature verification outcome. The blob is read using blobReader,
// upon successful verification, it returns the descriptor of the blob. // and upon successful verification, it returns the descriptor of the blob.
// 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
func VerifyBlob(ctx context.Context, blobVerifier BlobVerifier, blobReader io.Reader, signature []byte, verifyBlobOpts VerifyBlobOptions) (ocispec.Descriptor, *VerificationOutcome, error) { func VerifyBlob(ctx context.Context, blobVerifier BlobVerifier, blobReader io.Reader, signature []byte, verifyBlobOpts VerifyBlobOptions) (ocispec.Descriptor, *VerificationOutcome, error) {
if blobVerifier == nil { if blobVerifier == nil {
return ocispec.Descriptor{}, nil, errors.New("blobVerifier cannot be nil") return ocispec.Descriptor{}, nil, errors.New("blobVerifier cannot be nil")
} }
if blobReader == nil { if blobReader == nil {
return ocispec.Descriptor{}, nil, errors.New("blobReader cannot be nil") return ocispec.Descriptor{}, nil, errors.New("blobReader cannot be nil")
} }
if len(signature) == 0 { if len(signature) == 0 {
return ocispec.Descriptor{}, nil, errors.New("signature cannot be nil or empty") return ocispec.Descriptor{}, nil, errors.New("signature cannot be nil or empty")
} }
if err := validateContentMediaType(verifyBlobOpts.ContentMediaType); err != nil { if err := validateContentMediaType(verifyBlobOpts.ContentMediaType); err != nil {
return ocispec.Descriptor{}, nil, err return ocispec.Descriptor{}, nil, err
} }
if err := validateSigMediaType(verifyBlobOpts.SignatureMediaType); err != nil { if err := validateSigMediaType(verifyBlobOpts.SignatureMediaType); err != nil {
return ocispec.Descriptor{}, nil, err return ocispec.Descriptor{}, nil, err
} }
getDescFunc := getDescriptorFunc(ctx, blobReader, verifyBlobOpts.ContentMediaType, verifyBlobOpts.UserMetadata) getDescFunc := getDescriptorFunc(ctx, blobReader, verifyBlobOpts.ContentMediaType, verifyBlobOpts.UserMetadata)
vo, err := blobVerifier.VerifyBlob(ctx, getDescFunc, signature, verifyBlobOpts.BlobVerifierVerifyOptions) vo, err := blobVerifier.VerifyBlob(ctx, getDescFunc, signature, verifyBlobOpts.BlobVerifierVerifyOptions)
if err != nil { if err != nil {
@ -444,12 +459,11 @@ func VerifyBlob(ctx context.Context, blobVerifier BlobVerifier, blobReader io.Re
if err = json.Unmarshal(vo.EnvelopeContent.Payload.Content, &desc); err != nil { if err = json.Unmarshal(vo.EnvelopeContent.Payload.Content, &desc); err != nil {
return ocispec.Descriptor{}, nil, err return ocispec.Descriptor{}, nil, err
} }
return desc, vo, nil 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 return the // verification types (like integrity, authenticity, etc.) and returns 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
@ -473,7 +487,6 @@ 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)
@ -481,10 +494,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("Verification skipped for", verifyOpts.ArtifactReference) logger.Infoln("Signature verification skipped for", verifyOpts.ArtifactReference)
return ocispec.Descriptor{}, []*VerificationOutcome{{VerificationLevel: verificationLevel}}, nil return ocispec.Descriptor{}, []*VerificationOutcome{{VerificationLevel: verificationLevel}}, nil
} }
logger.Info("Check over. Trust policy is not configured to skip signature verification") logger.Info("Check over. The signature verification level is not set to 'skip' in the trust policy.")
} }
// get artifact descriptor // get artifact descriptor
@ -502,7 +515,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 `%s` before verification", ref.Reference, artifactDescriptor.Digest.String()) logger.Infof("Resolved artifact tag `%s` to digest `%v` before verification", ref.Reference, artifactDescriptor.Digest)
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())}
@ -547,6 +560,7 @@ 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}
@ -555,14 +569,11 @@ 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

View File

@ -307,8 +307,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} verifier := dummyVerifier{&policyDocument, mock.PluginManager{}, false, *trustpolicy.LevelStrict, false}
errorMessage := "network error" errorMessage := "network error"
expectedErr := ErrorSignatureRetrievalFailed{Msg: errorMessage} expectedErr := ErrorSignatureRetrievalFailed{Msg: errorMessage}
@ -326,8 +325,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} verifier := dummyVerifier{&policyDocument, mock.PluginManager{}, false, *trustpolicy.LevelStrict, false}
errorMessage := "reference is missing digest or tag" errorMessage := "reference is missing digest or tag"
expectedErr := ErrorSignatureRetrievalFailed{Msg: errorMessage} expectedErr := ErrorSignatureRetrievalFailed{Msg: errorMessage}
@ -343,7 +341,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} verifier := dummyVerifier{&policyDocument, mock.PluginManager{}, false, *trustpolicy.LevelStrict, false}
errorMessage := "invalid reference: invalid repository \"UPPERCASE/test\"" errorMessage := "invalid reference: invalid repository \"UPPERCASE/test\""
expectedErr := ErrorSignatureRetrievalFailed{Msg: errorMessage} expectedErr := ErrorSignatureRetrievalFailed{Msg: errorMessage}
@ -360,7 +358,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} verifier := dummyVerifier{&policyDocument, mock.PluginManager{}, false, *trustpolicy.LevelStrict, false}
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}
@ -384,7 +382,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 := fmt.Errorf(errorMessage) expectedErr := errors.New(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 {
@ -395,7 +393,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} verifier := dummyVerifier{&policyDocument, mock.PluginManager{}, false, *trustpolicy.LevelSkip, false}
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)
@ -408,7 +406,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} verifier := dummyVerifier{&policyDocument, mock.PluginManager{}, false, *trustpolicy.LevelStrict, false}
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}
@ -425,7 +423,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} verifier := dummyVerifier{&policyDocument, mock.PluginManager{}, false, *trustpolicy.LevelStrict, false}
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}
@ -442,21 +440,35 @@ 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} verifier := dummyVerifier{&policyDocument, mock.PluginManager{}, false, *trustpolicy.LevelStrict, false}
// 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("SignaureMediaTypeMismatch expected: %v got: %v", nil, err) t.Fatalf("expected nil error, but got: %v", 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} verifier := dummyVerifier{&policyDocument, mock.PluginManager{}, false, *trustpolicy.LevelStrict, false}
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
@ -472,7 +484,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} verifier := dummyVerifier{&policyDocument, mock.PluginManager{}, true, *trustpolicy.LevelStrict, false}
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)}
@ -489,7 +501,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} verifier := dummyVerifier{&policyDocument, mock.PluginManager{}, true, *trustpolicy.LevelStrict, false}
expectedErr := ErrorVerificationFailed{} expectedErr := ErrorVerificationFailed{}
// mock the repository // mock the repository
@ -516,7 +528,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} verifier := dummyVerifier{&policyDocument, mock.PluginManager{}, false, *trustpolicy.LevelStrict, false}
expectedErr := errors.New("repo cannot be nil") expectedErr := errors.New("repo cannot be nil")
// mock the repository // mock the repository
@ -582,20 +594,20 @@ func TestVerifyBlobValid(t *testing.T) {
_, _, err := VerifyBlob(context.Background(), &dummyVerifier{}, strings.NewReader("some content"), []byte("signature"), opts) _, _, err := VerifyBlob(context.Background(), &dummyVerifier{}, strings.NewReader("some content"), []byte("signature"), opts)
if err != nil { if err != nil {
t.Fatalf("SignaureMediaTypeMismatch expected: %v got: %v", nil, err) t.Fatalf("expected nil error, but got: %v", err)
} }
} }
func dummyPolicyDocument() (policyDoc trustpolicy.Document) { func dummyPolicyDocument() (policyDoc trustpolicy.OCIDocument) {
policyDoc = trustpolicy.Document{ policyDoc = trustpolicy.OCIDocument{
Version: "1.0", Version: "1.0",
TrustPolicies: []trustpolicy.TrustPolicy{dummyPolicyStatement()}, TrustPolicies: []trustpolicy.OCITrustPolicy{dummyPolicyStatement()},
} }
return return
} }
func dummyPolicyStatement() (policyStatement trustpolicy.TrustPolicy) { func dummyPolicyStatement() (policyStatement trustpolicy.OCITrustPolicy) {
policyStatement = trustpolicy.TrustPolicy{ policyStatement = trustpolicy.OCITrustPolicy{
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"},
@ -605,7 +617,6 @@ func dummyPolicyStatement() (policyStatement trustpolicy.TrustPolicy) {
return return
} }
type dummySigner struct { type dummySigner struct {
fail bool fail bool
} }
@ -655,6 +666,7 @@ type dummyVerifier struct {
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) {
@ -668,6 +680,13 @@ 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) { func (v *dummyVerifier) VerifyBlob(_ context.Context, _ BlobDescriptorGenerator, _ []byte, _ BlobVerifierVerifyOptions) (*VerificationOutcome, error) {
if v.FailVerify { if v.FailVerify {
return nil, errors.New("failed verify") return nil, errors.New("failed verify")
@ -744,7 +763,7 @@ func TestLocalContent(t *testing.T) {
MaxSignatureAttempts: math.MaxInt64, MaxSignatureAttempts: math.MaxInt64,
} }
policyDocument := dummyPolicyDocument() policyDocument := dummyPolicyDocument()
verifier := dummyVerifier{&policyDocument, mock.PluginManager{}, false, *trustpolicy.LevelStrict} verifier := dummyVerifier{&policyDocument, mock.PluginManager{}, false, *trustpolicy.LevelStrict, false}
// 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 {

View File

@ -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
} }

View File

@ -27,35 +27,43 @@ 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.
//
// Deprecated: GenericPlugin exists for historical compatibility and should not be used. // Deprecated: GenericPlugin exists for historical compatibility and should not be used.
// To access GenericPlugin, use the notation-plugin-framework-go's plugin.GenericPlugin type. // To access GenericPlugin, use the notation-plugin-framework-go's plugin.GenericPlugin type.
type GenericPlugin = plugin.GenericPlugin type GenericPlugin = plugin.GenericPlugin
// SignPlugin defines the required methods to be a SignPlugin. // SignPlugin defines the required methods to be a SignPlugin.
//
// Deprecated: SignPlugin exists for historical compatibility and should not be used. // Deprecated: SignPlugin exists for historical compatibility and should not be used.
// To access SignPlugin, use the notation-plugin-framework-go's plugin.SignPlugin type. // To access SignPlugin, use the notation-plugin-framework-go's plugin.SignPlugin type.
type SignPlugin = plugin.SignPlugin type SignPlugin = plugin.SignPlugin
// VerifyPlugin defines the required method to be a VerifyPlugin. // VerifyPlugin defines the required method to be a VerifyPlugin.
//
// Deprecated: VerifyPlugin exists for historical compatibility and should not be used. // Deprecated: VerifyPlugin exists for historical compatibility and should not be used.
// To access VerifyPlugin, use the notation-plugin-framework-go's plugin.VerifyPlugin type. // To access VerifyPlugin, use the notation-plugin-framework-go's plugin.VerifyPlugin type.
type VerifyPlugin = plugin.VerifyPlugin type VerifyPlugin = plugin.VerifyPlugin
// Plugin defines required methods to be a Plugin. // Plugin defines required methods to be a Plugin.
//
// Deprecated: Plugin exists for historical compatibility and should not be used. // Deprecated: Plugin exists for historical compatibility and should not be used.
// 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
@ -218,10 +226,15 @@ 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)
cmd.Stderr = &stderr // The limit writer will be handled by the caller in run() by comparing the
cmd.Stdout = &stdout // bytes written with the expected length of the bytes.
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

View File

@ -19,9 +19,11 @@ 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"
) )
@ -181,7 +183,7 @@ func TestValidateMetadata(t *testing.T) {
} }
} }
func TestNewCLIPlugin_PathError(t *testing.T) { func TestNewCLIPlugin_Error(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")
@ -203,6 +205,25 @@ func TestNewCLIPlugin_PathError(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) {

View File

@ -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/notaryproject/blob/efc828223710f99ab9639d2d0f72d59036a8e80c/specs/signature-specification.md#storage // spec: https://github.com/notaryproject/specifications/blob/v1.1.0/specs/signature-specification.md#signature
const ArtifactTypeNotation = "application/vnd.cncf.notary.signature" const ArtifactTypeNotation = "application/vnd.cncf.notary.signature"

View File

@ -14,19 +14,17 @@
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"
) )
@ -35,30 +33,17 @@ const (
maxManifestSizeLimit = 4 * 1024 * 1024 // 4 MiB maxManifestSizeLimit = 4 * 1024 * 1024 // 4 MiB
) )
var ( // RepositoryOptions provides user options when creating a [Repository]
// 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)
@ -68,7 +53,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{
@ -77,7 +62,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)
@ -108,7 +93,6 @@ 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)
@ -126,7 +110,6 @@ 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()
@ -179,6 +162,7 @@ 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
@ -193,55 +177,27 @@ 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 {
@ -257,6 +213,7 @@ 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
@ -264,6 +221,10 @@ 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:
@ -274,6 +235,7 @@ 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
@ -281,15 +243,39 @@ 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
} }
// 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 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"
if node.ArtifactType == ArtifactTypeNotation { // add the node to results
results = append(results, node) results = append(results, node)
} }
}
return results, nil return results, nil
} }

View File

@ -16,6 +16,7 @@ package registry
import ( import (
"bytes" "bytes"
"context" "context"
"errors"
"fmt" "fmt"
"io" "io"
"net/http" "net/http"
@ -140,7 +141,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{}, fmt.Errorf(errMsg) return &http.Response{}, errors.New(errMsg)
case "v2/test/manifest/" + validDigest2: case "v2/test/manifest/" + validDigest2:
return &http.Response{ return &http.Response{
StatusCode: http.StatusOK, StatusCode: http.StatusOK,
@ -164,7 +165,7 @@ func (c mockRemoteClient) Do(req *http.Request) (*http.Response, error) {
}, },
}, nil }, nil
default: default:
return &http.Response{}, fmt.Errorf(msg) return &http.Response{}, errors.New(msg)
} }
case "/v2/test/referrers/": case "/v2/test/referrers/":
return &http.Response{ return &http.Response{
@ -220,7 +221,7 @@ func (c mockRemoteClient) Do(req *http.Request) (*http.Response, error) {
} }
return resp, nil return resp, nil
} }
return &http.Response{}, fmt.Errorf(errMsg) return &http.Response{}, errors.New(errMsg)
} }
} }
@ -480,8 +481,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:baeaea44f55c94499b7e082bd3c98ad5ec40fdf23ef89cdf4e5db6b83e4f18f5", Digest: "sha256:64300ad03f1dcd18136787363f3069c9598623221cbe76e3233d35266b7973d6",
Size: 728, Size: 793,
} }
expectedSignatureBlobDesc = ocispec.Descriptor{ expectedSignatureBlobDesc = ocispec.Descriptor{
MediaType: joseTag, MediaType: joseTag,
@ -810,6 +811,31 @@ 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{},
@ -825,7 +851,6 @@ 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)
} }
@ -833,4 +858,172 @@ 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)
}
} }

View File

@ -35,7 +35,8 @@ import (
) )
// 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
@ -49,16 +50,17 @@ var algorithms = map[crypto.Hash]digest.Algorithm{
crypto.SHA512: digest.SHA512, crypto.SHA512: digest.SHA512,
} }
// NewFromPlugin creates a notation.Signer that signs artifacts and generates // 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 should not be used. //
// To create PluginSigner, use NewPluginSigner() function. // Deprecated: NewFromPlugin function exists for historical compatibility and
// should not be used. 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 notation.Signer that signs artifacts and generates // NewPluginSigner 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.
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) {
@ -68,7 +70,6 @@ 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,
@ -82,24 +83,21 @@ 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
// marshalled envelope. // signature and SignerInfo.
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)
@ -112,20 +110,25 @@ 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 arbitrary data and returns the marshalled envelope. // 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) { func (s *PluginSigner) SignBlob(ctx context.Context, descGenFunc notation.BlobDescriptorGenerator, 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
} }
// 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") logger.Debug("Invoking plugin's describe-key command")
ks, err := s.getKeySpec(ctx, mergedConfig) ks, err := s.getKeySpec(ctx, mergedConfig)
@ -138,14 +141,8 @@ func (s *PluginSigner) SignBlob(ctx context.Context, descGenFunc notation.BlobDe
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
logger.Debugf("Using plugin %v with capabilities %v to sign blob using descriptor %+v", metadata.Name, metadata.Capabilities, desc) logger.Debugf("Using plugin %v with capabilities %v to sign blob using descriptor %+v", metadata.Name, metadata.Capabilities, desc)
if metadata.HasCapability(plugin.CapabilitySignatureGenerator) {
return s.generateSignature(ctx, desc, opts, ks, metadata, mergedConfig) return s.generateSignature(ctx, desc, opts, ks, metadata, mergedConfig)
} else if metadata.HasCapability(plugin.CapabilityEnvelopeGenerator) {
return s.generateSignatureEnvelope(ctx, desc, opts)
}
return nil, nil, fmt.Errorf("plugin does not have signing capabilities")
} }
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) {
@ -155,11 +152,9 @@ 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)
} }
@ -175,7 +170,6 @@ 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)
} }
@ -188,6 +182,7 @@ 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,
@ -210,13 +205,11 @@ 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)
@ -224,31 +217,29 @@ 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
@ -266,7 +257,6 @@ 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
} }
@ -276,6 +266,7 @@ 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 {
@ -293,6 +284,7 @@ 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)
@ -349,12 +341,10 @@ 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,
@ -363,7 +353,6 @@ 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

View File

@ -216,6 +216,17 @@ 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
@ -352,6 +363,21 @@ func TestPluginSigner_SignBlob_Valid(t *testing.T) {
} }
} }
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) {

View File

@ -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 interface by providing builtinSigner for local signing and // [notation.Signer] and [notation.BlobSigner] interfaces by providing
// PluginSigner for remote signing. // builtinSigner for local signing and [PluginSigner] for remote signing.
package signer package signer
import ( import (
@ -36,19 +36,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.3.0+unreleased"
// GenericSigner implements notation.Signer and embeds signature.Signer // GenericSigner implements [notation.Signer] and [notation.BlobSigner].
// It embeds signature.Signer.
type GenericSigner struct { type GenericSigner struct {
signer signature.Signer signer signature.Signer
} }
// New returns a builtinSigner given key and cert chain // New returns a [notation.Signer] given key and cert chain.
// Deprecated: New function exists for historical compatibility and should not be used. //
// To create GenericSigner, use NewGenericSigner() function. // Deprecated: New function exists for historical compatibility and
// should not be used. To create [GenericSigner],
// 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 {
@ -59,12 +62,13 @@ func NewGenericSigner(key crypto.PrivateKey, certChain []*x509.Certificate) (*Ge
}, nil }, nil
} }
// NewFromFiles returns a builtinSigner given key and certChain paths. // NewFromFiles returns a [notation.Signer] 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 paths. // NewGenericSignerFromFiles returns a builtinSigner given key and certChain
// 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")
@ -96,7 +100,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
// marshalled envelope. // signature and SignerInfo.
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)
@ -106,7 +110,6 @@ 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
@ -130,6 +133,7 @@ func (s *GenericSigner) Sign(ctx context.Context, desc ocispec.Descriptor, opts
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
@ -143,6 +147,12 @@ 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)
@ -152,12 +162,10 @@ 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)
@ -168,29 +176,26 @@ 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 blobGen and returns the marshalled envelope // SignBlob signs the descriptor returned by genDesc, and returns the
func (s *GenericSigner) SignBlob(ctx context.Context, descGenFunc notation.BlobDescriptorGenerator, opts notation.SignerSignOptions) ([]byte, *signature.SignerInfo, error) { // 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 := log.GetLogger(ctx)
logger.Debugf("Generic blob signing for signature media type %v", opts.SignatureMediaType) logger.Debugf("Generic blob signing for signature media type %s", opts.SignatureMediaType)
ks, err := s.signer.KeySpec() ks, err := s.signer.KeySpec()
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
desc, err := getDescriptor(ks, genDesc)
desc, err := getDescriptor(ks, descGenFunc)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
return s.Sign(ctx, desc, opts) return s.Sign(ctx, desc, opts)
} }
func getDescriptor(ks signature.KeySpec, descGenFunc notation.BlobDescriptorGenerator) (ocispec.Descriptor, error) { func getDescriptor(ks signature.KeySpec, genDesc notation.BlobDescriptorGenerator) (ocispec.Descriptor, error) {
digestAlg, ok := algorithms[ks.SignatureAlgorithm().Hash()] digestAlg, ok := algorithms[ks.SignatureAlgorithm().Hash()]
if !ok { if !ok {
return ocispec.Descriptor{}, fmt.Errorf("unknown hashing algo %v", ks.SignatureAlgorithm().Hash()) return ocispec.Descriptor{}, fmt.Errorf("unknown hashing algo %v", ks.SignatureAlgorithm().Hash())
} }
return genDesc(digestAlg)
return descGenFunc(digestAlg)
} }

View File

@ -30,6 +30,8 @@ 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"
@ -53,7 +55,8 @@ type keyCertPair struct {
var keyCertPairCollections []*keyCertPair var keyCertPairCollections []*keyCertPair
// setUpKeyCertPairCollections setups all combinations of private key and certificates. // setUpKeyCertPairCollections setups all combinations of private key and
// certificates.
func setUpKeyCertPairCollections() []*keyCertPair { func setUpKeyCertPairCollections() []*keyCertPair {
// rsa // rsa
var keyCertPairs []*keyCertPair var keyCertPairs []*keyCertPair
@ -160,6 +163,17 @@ 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()
@ -257,6 +271,27 @@ 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) { func TestSignBlobWithCertChain(t *testing.T) {

View File

@ -110,11 +110,11 @@ func (c *FileCache) Get(ctx context.Context, url string) (*corecrl.Bundle, error
// check expiry // check expiry
if err := checkExpiry(ctx, bundle.BaseCRL.NextUpdate); err != nil { if err := checkExpiry(ctx, bundle.BaseCRL.NextUpdate); err != nil {
return nil, err return nil, fmt.Errorf("check BaseCRL expiry failed: %w", err)
} }
if bundle.DeltaCRL != nil { if bundle.DeltaCRL != nil {
if err := checkExpiry(ctx, bundle.DeltaCRL.NextUpdate); err != nil { if err := checkExpiry(ctx, bundle.DeltaCRL.NextUpdate); err != nil {
return nil, err return nil, fmt.Errorf("check DeltaCRL expiry failed: %w", err)
} }
} }
@ -144,7 +144,7 @@ func (c *FileCache) Set(ctx context.Context, url string, bundle *corecrl.Bundle)
if err != nil { if err != nil {
return fmt.Errorf("failed to store crl bundle in file cache: %w", err) return fmt.Errorf("failed to store crl bundle in file cache: %w", err)
} }
if err := file.WriteFile(filepath.Join(c.root, c.fileName(url)), contentBytes); err != nil { 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 fmt.Errorf("failed to store crl bundle in file cache: %w", err)
} }
return nil return nil

View File

@ -270,7 +270,7 @@ func TestGetFailed(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
_, err = cache.Get(ctx, "expiredKey") _, err = cache.Get(ctx, "expiredKey")
expectedErrMsg := "crl bundle retrieved from file cache does not contain valid NextUpdate" expectedErrMsg := "check BaseCRL expiry failed: crl bundle retrieved from file cache does not contain valid NextUpdate"
if err == nil || err.Error() != expectedErrMsg { if err == nil || err.Error() != expectedErrMsg {
t.Fatalf("expected %s, but got %v", expectedErrMsg, err) t.Fatalf("expected %s, but got %v", expectedErrMsg, err)
} }

View File

@ -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 VerificationResult fails the entire // isCriticalFailure checks whether a [notation.ValidationResult] fails the
// signature verification workflow. // entire signature verification workflow.
// signature verification workflow is considered failed if there is a // signature verification workflow is considered failed if there is a
// VerificationResult with "Enforced" as the action but the result was // ValidationResult 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

View File

@ -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" 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)"
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)
} }

View File

@ -24,7 +24,7 @@ import (
"github.com/notaryproject/notation-go/internal/slices" "github.com/notaryproject/notation-go/internal/slices"
) )
// BlobDocument represents a trustpolicy.blob.json document // BlobDocument represents a trustpolicy.blob.json document for arbitrary blobs
type BlobDocument struct { type BlobDocument struct {
// Version of the policy document // Version of the policy document
Version string `json:"version"` Version string `json:"version"`
@ -33,7 +33,8 @@ type BlobDocument struct {
TrustPolicies []BlobTrustPolicy `json:"trustPolicies"` TrustPolicies []BlobTrustPolicy `json:"trustPolicies"`
} }
// BlobTrustPolicy represents a policy statement in the blob policy document // BlobTrustPolicy represents a policy statement in the blob trust policy
// document
type BlobTrustPolicy struct { type BlobTrustPolicy struct {
// Name of the policy statement // Name of the policy statement
Name string `json:"name"` Name string `json:"name"`
@ -53,15 +54,16 @@ type BlobTrustPolicy struct {
var supportedBlobPolicyVersions = []string{"1.0"} var supportedBlobPolicyVersions = []string{"1.0"}
// LoadBlobDocument loads a trust policy document from a local file system // LoadBlobDocument loads a blob trust policy document from a local file system
func LoadBlobDocument() (*BlobDocument, error) { func LoadBlobDocument() (*BlobDocument, error) {
var doc BlobDocument var doc BlobDocument
err := getDocument(dir.PathBlobTrustPolicy, &doc) err := getDocument(dir.PathBlobTrustPolicy, &doc)
return &doc, err return &doc, err
} }
// Validate validates a policy document according to its version's rule set. // Validate validates a blob trust policy document according to its version's
// if any rule is violated, returns an error // rule set.
// If any rule is violated, returns an error.
func (policyDoc *BlobDocument) Validate() error { func (policyDoc *BlobDocument) Validate() error {
// sanity check // sanity check
if policyDoc == nil { if policyDoc == nil {
@ -70,7 +72,7 @@ func (policyDoc *BlobDocument) Validate() error {
// Validate Version // Validate Version
if policyDoc.Version == "" { if policyDoc.Version == "" {
return errors.New("blob trust policy has empty version, version must be specified") return errors.New("blob trust policy document has empty version, version must be specified")
} }
if !slices.Contains(supportedBlobPolicyVersions, policyDoc.Version) { if !slices.Contains(supportedBlobPolicyVersions, policyDoc.Version) {
return fmt.Errorf("blob trust policy document uses unsupported version %q", policyDoc.Version) return fmt.Errorf("blob trust policy document uses unsupported version %q", policyDoc.Version)
@ -80,7 +82,6 @@ func (policyDoc *BlobDocument) Validate() error {
if len(policyDoc.TrustPolicies) == 0 { if len(policyDoc.TrustPolicies) == 0 {
return errors.New("blob trust policy document can not have zero trust policy statements") return errors.New("blob trust policy document can not have zero trust policy statements")
} }
policyNames := set.New[string]() policyNames := set.New[string]()
var foundGlobalPolicy bool var foundGlobalPolicy bool
for _, statement := range policyDoc.TrustPolicies { for _, statement := range policyDoc.TrustPolicies {
@ -88,11 +89,9 @@ func (policyDoc *BlobDocument) Validate() error {
if policyNames.Contains(statement.Name) { 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) 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 { if err := validatePolicyCore(statement.Name, statement.SignatureVerification, statement.TrustStores, statement.TrustedIdentities); err != nil {
return fmt.Errorf("blob trust policy: %w", err) return fmt.Errorf("blob trust policy: %w", err)
} }
if statement.GlobalPolicy { if statement.GlobalPolicy {
if foundGlobalPolicy { 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") return errors.New("multiple blob trust policy statements have globalPolicy set to true. Only one trust policy statement can be marked as global policy")
@ -102,17 +101,16 @@ func (policyDoc *BlobDocument) Validate() error {
if reflect.DeepEqual(statement.SignatureVerification.VerificationLevel, LevelSkip) { if reflect.DeepEqual(statement.SignatureVerification.VerificationLevel, LevelSkip) {
return errors.New("global blob trust policy statement cannot have verification level set to skip") return errors.New("global blob trust policy statement cannot have verification level set to skip")
} }
foundGlobalPolicy = true foundGlobalPolicy = true
} }
policyNames.Add(statement.Name) policyNames.Add(statement.Name)
} }
return nil return nil
} }
// GetApplicableTrustPolicy returns a pointer to the deep copied TrustPolicy for given policy name // GetApplicableTrustPolicy returns a pointer to the deep copied [BlobTrustPolicy]
// see https://github.com/notaryproject/notaryproject/blob/v1.1.0/specs/trust-store-trust-policy.md#blob-trust-policy // 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) { func (policyDoc *BlobDocument) GetApplicableTrustPolicy(policyName string) (*BlobTrustPolicy, error) {
if strings.TrimSpace(policyName) == "" { if strings.TrimSpace(policyName) == "" {
return nil, errors.New("policy name cannot be empty") return nil, errors.New("policy name cannot be empty")
@ -123,23 +121,22 @@ func (policyDoc *BlobDocument) GetApplicableTrustPolicy(policyName string) (*Blo
return (&policyStatement).clone(), nil return (&policyStatement).clone(), nil
} }
} }
return nil, fmt.Errorf("no applicable blob trust policy with name %q", policyName) return nil, fmt.Errorf("no applicable blob trust policy with name %q", policyName)
} }
// GetGlobalTrustPolicy returns a pointer to the deep copy of the TrustPolicy that is marked as global policy // GetGlobalTrustPolicy returns a pointer to the deep copied [BlobTrustPolicy]
// see https://github.com/notaryproject/notaryproject/blob/v1.1.0/specs/trust-store-trust-policy.md#blob-trust-policy // 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) { func (policyDoc *BlobDocument) GetGlobalTrustPolicy() (*BlobTrustPolicy, error) {
for _, policyStatement := range policyDoc.TrustPolicies { for _, policyStatement := range policyDoc.TrustPolicies {
if policyStatement.GlobalPolicy { if policyStatement.GlobalPolicy {
return (&policyStatement).clone(), nil return (&policyStatement).clone(), nil
} }
} }
return nil, fmt.Errorf("no global blob trust policy") return nil, fmt.Errorf("no global blob trust policy")
} }
// clone returns a pointer to the deeply copied TrustPolicy // clone returns a pointer to the deep copied [BlobTrustPolicy]
func (t *BlobTrustPolicy) clone() *BlobTrustPolicy { func (t *BlobTrustPolicy) clone() *BlobTrustPolicy {
return &BlobTrustPolicy{ return &BlobTrustPolicy{
Name: t.Name, Name: t.Name,

View File

@ -57,7 +57,7 @@ func TestValidate_BlobDocument_Error(t *testing.T) {
policyDoc := dummyBlobPolicyDocument() policyDoc := dummyBlobPolicyDocument()
policyDoc.Version = "" policyDoc.Version = ""
err = policyDoc.Validate() err = policyDoc.Validate()
if err == nil || err.Error() != "blob trust policy has empty version, version must be specified" { if err == nil || err.Error() != "blob trust policy document has empty version, version must be specified" {
t.Fatalf("empty version should return error") t.Fatalf("empty version should return error")
} }

View File

@ -25,7 +25,7 @@ import (
"github.com/notaryproject/notation-go/internal/trustpolicy" "github.com/notaryproject/notation-go/internal/trustpolicy"
) )
// OCIDocument represents a trustPolicy.json document for OCI artifacts // OCIDocument represents a trustpolicy.oci.json document for OCI artifacts
type OCIDocument struct { type OCIDocument struct {
// Version of the policy document // Version of the policy document
Version string `json:"version"` Version string `json:"version"`
@ -34,7 +34,7 @@ type OCIDocument struct {
TrustPolicies []OCITrustPolicy `json:"trustPolicies"` TrustPolicies []OCITrustPolicy `json:"trustPolicies"`
} }
// OCITrustPolicy represents a policy statement in the policy document for OCI artifacts // OCITrustPolicy represents a policy statement in the OCI trust policy document
type OCITrustPolicy struct { type OCITrustPolicy struct {
// Name of the policy statement // Name of the policy statement
Name string `json:"name"` Name string `json:"name"`
@ -53,28 +53,33 @@ type OCITrustPolicy struct {
} }
// Document represents a trustPolicy.json document // Document represents a trustPolicy.json document
// Deprecated: Document exists for historical compatibility and should not be used. //
// To create OCI Document, use OCIDocument. // Deprecated: Document exists for historical compatibility and
// should not be used. To create OCI Document, use [OCIDocument].
type Document = OCIDocument type Document = OCIDocument
// TrustPolicy represents a policy statement in the policy document // TrustPolicy represents a policy statement in the policy document
// Deprecated: TrustPolicy exists for historical compatibility and should not be used. //
// To create OCI TrustPolicy, use OCITrustPolicy. // Deprecated: TrustPolicy exists for historical compatibility and
// should not be used. To create OCI TrustPolicy, use [OCITrustPolicy].
type TrustPolicy = OCITrustPolicy type TrustPolicy = OCITrustPolicy
// LoadDocument loads a trust policy document from a local file system // 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. // Deprecated: LoadDocument function exists for historical compatibility and
// should not be used. To load OCI Document, use [LoadOCIDocument] function.
var LoadDocument = LoadOCIDocument var LoadDocument = LoadOCIDocument
var supportedOCIPolicyVersions = []string{"1.0"} var supportedOCIPolicyVersions = []string{"1.0"}
// LoadOCIDocument retrieves a trust policy document from the local file system. // 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. // It attempts to read from [dir.PathOCITrustPolicy] first; if not found,
// If both dir.PathOCITrustPolicy and dir.PathTrustPolicy exist, dir.PathOCITrustPolicy will be read. // it tries [dir.PathTrustPolicy].
// If both dir.PathOCITrustPolicy and dir.PathTrustPolicy exist,
// dir.PathOCITrustPolicy will be read.
func LoadOCIDocument() (*OCIDocument, error) { func LoadOCIDocument() (*OCIDocument, error) {
var doc OCIDocument var doc OCIDocument
// attempt to load the document from dir.PathOCITrustPolicy // attempt to load the document from dir.PathOCITrustPolicy
if err := getDocument(dir.PathOCITrustPolicy, &doc); err != nil { if err := getDocument(dir.PathOCITrustPolicy, &doc); err != nil {
// if the document is not found at the first path, try the second path // if the document is not found at the first path, try the second path
@ -84,10 +89,10 @@ func LoadOCIDocument() (*OCIDocument, error) {
} }
return &doc, nil return &doc, nil
} }
// if an error occurred other than the document not found, return it // if an error occurred other than the document not found, return it
return nil, err return nil, err
} }
return &doc, nil return &doc, nil
} }
@ -111,18 +116,15 @@ func (policyDoc *OCIDocument) Validate() error {
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("oci 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 oci 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("oci trust policy: %w", err)
} }
policyNames.Add(statement.Name) policyNames.Add(statement.Name)
} }
@ -130,14 +132,13 @@ 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 TrustPolicy // GetApplicableTrustPolicy returns a pointer to the deep copied [OCITrustPolicy]
// 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/notaryproject/blob/v1.0.0/specs/trust-store-trust-policy.md#selecting-a-trust-policy-based-on-artifact-uri // see https://github.com/notaryproject/specifications/tree/9c81dc773508dedc5a81c02c8d805de04f65050b/specs/trust-store-trust-policy.md#selecting-a-trust-policy-based-on-artifact-uri
func (policyDoc *OCIDocument) GetApplicableTrustPolicy(artifactReference string) (*OCITrustPolicy, error) { func (policyDoc *OCIDocument) GetApplicableTrustPolicy(artifactReference string) (*OCITrustPolicy, error) {
artifactPath, err := getArtifactPathFromReference(artifactReference) artifactPath, err := getArtifactPathFromReference(artifactReference)
if err != nil { if err != nil {
@ -155,7 +156,6 @@ 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.
@ -167,7 +167,7 @@ func (policyDoc *OCIDocument) GetApplicableTrustPolicy(artifactReference string)
} }
} }
// clone returns a pointer to the deeply copied TrustPolicy // clone returns a pointer to the deep copied [OCITrustPolicy]
func (t *OCITrustPolicy) clone() *OCITrustPolicy { func (t *OCITrustPolicy) clone() *OCITrustPolicy {
return &OCITrustPolicy{ return &OCITrustPolicy{
Name: t.Name, Name: t.Name,
@ -218,7 +218,6 @@ 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
@ -242,12 +241,10 @@ 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)
} }

View File

@ -158,8 +158,9 @@ 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 struct for the given // GetVerificationLevel returns [VerificationLevel] for the given
// SignatureVerification struct throws error if SignatureVerification is invalid // [SignatureVerification] struct.
// 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")
@ -174,16 +175,13 @@ 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),
@ -218,13 +216,11 @@ 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
@ -244,12 +240,10 @@ 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) {
@ -322,7 +316,6 @@ 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
} }
@ -341,7 +334,6 @@ 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 {
@ -380,12 +372,11 @@ 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) {

View File

@ -15,6 +15,7 @@
package truststore package truststore
import ( import (
"bytes"
"context" "context"
"crypto/x509" "crypto/x509"
"errors" "errors"
@ -29,8 +30,7 @@ import (
"github.com/notaryproject/notation-go/internal/slices" "github.com/notaryproject/notation-go/internal/slices"
) )
// Type is an enum for trust store types supported such as // Type is an enum for trust store types supported
// "ca" and "signingAuthority"
type Type string type Type string
const ( const (
@ -47,18 +47,18 @@ var (
} }
) )
// X509TrustStore provide list and get behaviors for the trust store // X509TrustStore provides 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,6 +106,14 @@ 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 {
@ -137,3 +145,14 @@ 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
}

View File

@ -98,3 +98,31 @@ 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)
}
})
}

View File

@ -11,7 +11,8 @@
// 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 an implementation of notation.Verifier interface // Package verifier provides implementations of [notation.Verifier] and
// [notation.BlobVerifier] interfaces.
package verifier package verifier
import ( import (
@ -57,7 +58,8 @@ var algorithms = map[crypto.Hash]digest.Algorithm{
crypto.SHA512: digest.SHA512, crypto.SHA512: digest.SHA512,
} }
// verifier implements notation.Verifier, notation.BlobVerifier and notation.verifySkipper // verifier implements [notation.Verifier], [notation.BlobVerifier] and
// notation.verifySkipper interfaces.
type verifier struct { type verifier struct {
ociTrustPolicyDoc *trustpolicy.OCIDocument ociTrustPolicyDoc *trustpolicy.OCIDocument
blobTrustPolicyDoc *trustpolicy.BlobDocument blobTrustPolicyDoc *trustpolicy.BlobDocument
@ -69,7 +71,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 [NewVerifierWithOptions] 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
@ -86,9 +88,18 @@ 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 a OCI verifier based on local file system // NewOCIVerifierFromConfig returns an OCI verifier based on local file system
func NewOCIVerifierFromConfig() (*verifier, error) { func NewOCIVerifierFromConfig() (*verifier, error) {
// load trust policy // load trust policy
policyDocument, err := trustpolicy.LoadOCIDocument() policyDocument, err := trustpolicy.LoadOCIDocument()
@ -98,12 +109,15 @@ func NewOCIVerifierFromConfig() (*verifier, error) {
// load trust store // load trust store
x509TrustStore := truststore.NewX509TrustStore(dir.ConfigFS()) x509TrustStore := truststore.NewX509TrustStore(dir.ConfigFS())
return NewVerifier(policyDocument, nil, x509TrustStore, plugin.NewCLIManager(dir.PluginFS())) return NewVerifierWithOptions(x509TrustStore, VerifierOptions{
OCITrustPolicy: policyDocument,
PluginManager: plugin.NewCLIManager(dir.PluginFS()),
})
} }
// NewBlobVerifierFromConfig returns a Blob verifier based on local file system // NewBlobVerifierFromConfig returns a Blob verifier based on local file system
func NewBlobVerifierFromConfig() (*verifier, error) { func NewBlobVerifierFromConfig() (*verifier, error) {
// load trust policy // load blob trust policy
policyDocument, err := trustpolicy.LoadBlobDocument() policyDocument, err := trustpolicy.LoadBlobDocument()
if err != nil { if err != nil {
return nil, err return nil, err
@ -111,26 +125,28 @@ func NewBlobVerifierFromConfig() (*verifier, error) {
// load trust store // load trust store
x509TrustStore := truststore.NewX509TrustStore(dir.ConfigFS()) x509TrustStore := truststore.NewX509TrustStore(dir.ConfigFS())
return NewVerifier(nil, policyDocument, x509TrustStore, plugin.NewCLIManager(dir.PluginFS())) 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 ociTrustPolicy, trustStore,
// pluginManager, and VerifierOptions. // pluginManager, and VerifierOptions.
// //
// Deprecated: NewWithOptions function exists for historical compatibility and should not be used. // Deprecated: NewWithOptions function exists for historical compatibility and
// To create verifier, use NewVerifierWithOptions function. // 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) { func NewWithOptions(ociTrustPolicy *trustpolicy.OCIDocument, trustStore truststore.X509TrustStore, pluginManager plugin.Manager, opts VerifierOptions) (notation.Verifier, error) {
return NewVerifierWithOptions(ociTrustPolicy, nil, trustStore, pluginManager, opts) opts.OCITrustPolicy = ociTrustPolicy
opts.PluginManager = pluginManager
return NewVerifierWithOptions(trustStore, opts)
} }
// NewVerifier creates a new verifier given ociTrustPolicy, trustStore and pluginManager // NewVerifierWithOptions creates a new verifier given trustStore and
func NewVerifier(ociTrustPolicy *trustpolicy.OCIDocument, blobTrustPolicy *trustpolicy.BlobDocument, trustStore truststore.X509TrustStore, pluginManager plugin.Manager) (*verifier, error) { // verifierOptions.
return NewVerifierWithOptions(ociTrustPolicy, blobTrustPolicy, trustStore, pluginManager, VerifierOptions{}) func NewVerifierWithOptions(trustStore truststore.X509TrustStore, verifierOptions VerifierOptions) (*verifier, error) {
} ociTrustPolicy := verifierOptions.OCITrustPolicy
blobTrustPolicy := verifierOptions.BlobTrustPolicy
// NewVerifierWithOptions creates a new verifier given ociTrustPolicy, blobTrustPolicy,
// trustStore, pluginManager, and verifierOptions
func NewVerifierWithOptions(ociTrustPolicy *trustpolicy.OCIDocument, blobTrustPolicy *trustpolicy.BlobDocument, trustStore truststore.X509TrustStore, pluginManager plugin.Manager, verifierOptions VerifierOptions) (*verifier, error) {
if trustStore == nil { if trustStore == nil {
return nil, errors.New("trustStore cannot be nil") return nil, errors.New("trustStore cannot be nil")
} }
@ -151,7 +167,7 @@ func NewVerifierWithOptions(ociTrustPolicy *trustpolicy.OCIDocument, blobTrustPo
ociTrustPolicyDoc: ociTrustPolicy, ociTrustPolicyDoc: ociTrustPolicy,
blobTrustPolicyDoc: blobTrustPolicy, 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 {
@ -160,20 +176,24 @@ func NewVerifierWithOptions(ociTrustPolicy *trustpolicy.OCIDocument, blobTrustPo
return v, nil return v, nil
} }
// NewFromConfig returns a OCI verifier based on local file system. // NewFromConfig returns an OCI verifier based on local file system.
// //
// Deprecated: NewFromConfig function exists for historical compatibility and should not be used. // Deprecated: NewFromConfig function exists for historical compatibility and
// To create an OCI verifier, use NewOCIVerifierFromConfig function. // should not be used. To create an OCI verifier, use [NewOCIVerifierFromConfig]
// function.
func NewFromConfig() (notation.Verifier, error) { func NewFromConfig() (notation.Verifier, error) {
return NewOCIVerifierFromConfig() return NewOCIVerifierFromConfig()
} }
// New creates a new verifier given ociTrustPolicy, trustStore and pluginManager. // New creates a new verifier given ociTrustPolicy, trustStore and pluginManager.
// //
// Deprecated: New function exists for historical compatibility and should not be used. // Deprecated: New function exists for historical compatibility and
// To create verifier, use NewVerifier function. // should not be used. To create verifier, use [NewVerifier] function.
func New(ociTrustPolicy *trustpolicy.OCIDocument, trustStore truststore.X509TrustStore, pluginManager plugin.Manager) (notation.Verifier, error) { func New(ociTrustPolicy *trustpolicy.OCIDocument, trustStore truststore.X509TrustStore, pluginManager plugin.Manager) (notation.Verifier, error) {
return NewVerifier(ociTrustPolicy, nil, trustStore, pluginManager) return NewVerifierWithOptions(trustStore, VerifierOptions{
OCITrustPolicy: ociTrustPolicy,
PluginManager: pluginManager,
})
} }
// setRevocation sets revocation validators of v // setRevocation sets revocation validators of v
@ -319,7 +339,7 @@ func (v *verifier) VerifyBlob(ctx context.Context, descGenFunc notation.BlobDesc
return outcome, outcome.Error return outcome, outcome.Error
} }
// Verify verifies the signature associated the target OCI // 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',
@ -407,7 +427,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 %s", verificationPluginName) logger.Debugf("Finding verification plugin %q", 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)}
@ -812,7 +832,7 @@ func revocationFinalResult(certResults []*revocationresult.CertRevocationResult,
certResult := certResults[i] certResult := certResults[i]
if certResult.RevocationMethod == revocationresult.RevocationMethodOCSPFallbackCRL { if certResult.RevocationMethod == revocationresult.RevocationMethodOCSPFallbackCRL {
// log the fallback warning // log the fallback warning
logger.Warnf("OCSP check failed with unknown error and fallback to CRL check for certificate #%d in chain with subject %v", (i + 1), cert.Subject.String()) 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 { for _, serverResult := range certResult.ServerResults {
if serverResult.Error != nil { if serverResult.Error != nil {
@ -821,10 +841,10 @@ func revocationFinalResult(certResults []*revocationresult.CertRevocationResult,
// when the final revocation method is OCSPFallbackCRL, // when the final revocation method is OCSPFallbackCRL,
// the OCSP server results should not be logged as an error // the OCSP server results should not be logged as an error
// since the CRL revocation check can succeed. // since the CRL revocation check can succeed.
logger.Debugf("Certificate #%d in chain with subject %v encountered an error for revocation method %s at URL %q: %v", (i + 1), cert.Subject.String(), revocationresult.RevocationMethodOCSP, serverResult.Server, serverResult.Error) 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 continue
} }
logger.Errorf("Certificate #%d in chain with subject %v encountered an error for revocation method %s at URL %q: %v", (i + 1), cert.Subject.String(), serverResult.RevocationMethod, serverResult.Server, serverResult.Error) 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)
} }
} }
@ -838,6 +858,10 @@ func revocationFinalResult(certResults []*revocationresult.CertRevocationResult,
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
@ -1018,7 +1042,7 @@ func verifyTimestamp(ctx context.Context, policyName string, trustStores []strin
} }
// Performing timestamp verification // Performing timestamp verification
logger.Info("Performing timestamp verification...") logger.Debug("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...")
@ -1064,7 +1088,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.Info("TSA identity is: ", tsaCertChain[0].Subject) logger.Debug("The subject of TSA signing certificate 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...")
@ -1076,6 +1100,9 @@ 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
@ -1098,5 +1125,6 @@ func verifyTimestamp(ctx context.Context, policyName string, trustStores []strin
} }
// success // success
logger.Debug("Timestamp verification: Success")
return nil return nil
} }

View File

@ -17,10 +17,12 @@ import (
"context" "context"
"crypto/x509" "crypto/x509"
"crypto/x509/pkix" "crypto/x509/pkix"
"encoding/json"
"encoding/pem" "encoding/pem"
"errors" "errors"
"fmt" "fmt"
"net/http" "net/http"
"os"
"path/filepath" "path/filepath"
"reflect" "reflect"
"strconv" "strconv"
@ -223,7 +225,7 @@ func assertNotationVerification(t *testing.T, scheme signature.SigningScheme) {
for _, level := range verificationLevels { for _, level := range verificationLevels {
policyDocument := dummyOCIPolicyDocument() policyDocument := dummyOCIPolicyDocument()
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("signature is not produced by a trusted signer") expectedErr := fmt.Errorf("the signature's certificate chain does not contain any trusted certificate")
testCases = append(testCases, testCase{ testCases = append(testCases, testCase{
signatureBlob: validSigEnv, signatureBlob: validSigEnv,
verificationType: trustpolicy.TypeAuthenticity, verificationType: trustpolicy.TypeAuthenticity,
@ -728,9 +730,13 @@ func TestNewVerifierWithOptions(t *testing.T) {
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(&ociPolicy, &blobPolicy, store, pm, opts) v, err := NewVerifierWithOptions(store, VerifierOptions{
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 NewVerifierWithOptions constructor to succeed, but got %v", err)
} }
@ -750,18 +756,28 @@ func TestNewVerifierWithOptions(t *testing.T) {
t.Fatal("expected nil revocationCodeSigningValidator") t.Fatal("expected nil revocationCodeSigningValidator")
} }
_, err = NewVerifierWithOptions(nil, &blobPolicy, store, pm, opts) _, err = NewVerifierWithOptions(store, VerifierOptions{
RevocationClient: r,
BlobTrustPolicy: &blobPolicy,
PluginManager: pm,
})
if err != nil { if err != nil {
t.Fatalf("expected NewVerifierWithOptions constructor to succeed, but got %v", err) t.Fatalf("expected NewVerifierWithOptions constructor to succeed, but got %v", err)
} }
_, err = NewVerifierWithOptions(&ociPolicy, nil, store, pm, opts) _, err = NewVerifierWithOptions(store, VerifierOptions{
RevocationClient: r,
OCITrustPolicy: &ociPolicy,
PluginManager: pm,
})
if err != nil { if err != nil {
t.Fatalf("expected NewVerifierWithOptions constructor to succeed, but got %v", err) t.Fatalf("expected NewVerifierWithOptions constructor to succeed, but got %v", err)
} }
opts.RevocationClient = nil _, err = NewVerifierWithOptions(store, VerifierOptions{
_, err = NewVerifierWithOptions(&ociPolicy, nil, store, pm, opts) OCITrustPolicy: &ociPolicy,
PluginManager: pm,
})
if err != nil { if err != nil {
t.Fatalf("expected NewVerifierWithOptions constructor to succeed, but got %v", err) t.Fatalf("expected NewVerifierWithOptions constructor to succeed, but got %v", err)
} }
@ -770,19 +786,11 @@ func TestNewVerifierWithOptions(t *testing.T) {
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
opts = VerifierOptions{ v, err = NewVerifierWithOptions(store, VerifierOptions{
RevocationCodeSigningValidator: csValidator, RevocationCodeSigningValidator: csValidator,
} OCITrustPolicy: &ociPolicy,
v, err = NewVerifierWithOptions(&ociPolicy, nil, store, pm, opts) PluginManager: pm,
if err != nil { })
t.Fatalf("expected NewVerifierWithOptions constructor to succeed, but got %v", err)
}
if v.revocationCodeSigningValidator == nil {
t.Fatal("expected v.revocationCodeSigningValidator to be non-nil")
}
opts = VerifierOptions{}
v, err = NewVerifierWithOptions(&ociPolicy, nil, store, pm, opts)
if err != nil { if err != nil {
t.Fatalf("expected NewVerifierWithOptions constructor to succeed, but got %v", err) t.Fatalf("expected NewVerifierWithOptions constructor to succeed, but got %v", err)
} }
@ -803,22 +811,68 @@ 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,
})
_, err = NewVerifierWithOptions(nil, nil, store, pm, opts)
if err == nil || err.Error() != "ociTrustPolicy and blobTrustPolicy both cannot be nil" { if err == nil || err.Error() != "ociTrustPolicy and blobTrustPolicy both cannot be nil" {
t.Errorf("expected err but not found.") t.Errorf("expected err but not found.")
} }
_, err = NewVerifierWithOptions(&ociPolicy, &blobPolicy, nil, pm, opts) _, err = NewVerifierWithOptions(nil, VerifierOptions{
RevocationClient: r,
RevocationTimestampingValidator: rt,
OCITrustPolicy: &ociPolicy,
BlobTrustPolicy: &blobPolicy,
PluginManager: pm,
})
if err == nil || err.Error() != "trustStore cannot be nil" { if err == nil || err.Error() != "trustStore cannot be nil" {
t.Errorf("expected err but not found.") 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) { func TestVerifyBlob(t *testing.T) {
policy := &trustpolicy.BlobDocument{ policy := &trustpolicy.BlobDocument{
Version: "1.0", Version: "1.0",
@ -831,7 +885,10 @@ func TestVerifyBlob(t *testing.T) {
}, },
}, },
} }
v, err := NewVerifier(nil, policy, &testTrustStore{}, pm) v, err := NewVerifierWithOptions(&testTrustStore{}, VerifierOptions{
BlobTrustPolicy: policy,
PluginManager: pm,
})
if err != nil { if err != nil {
t.Fatalf("unexpected error while creating verifier: %v", err) t.Fatalf("unexpected error while creating verifier: %v", err)
} }
@ -877,7 +934,10 @@ func TestVerifyBlob_Error(t *testing.T) {
}, },
}, },
} }
v, err := NewVerifier(nil, policy, &testTrustStore{}, pm) v, err := NewVerifierWithOptions(&testTrustStore{}, VerifierOptions{
BlobTrustPolicy: policy,
PluginManager: pm,
})
if err != nil { if err != nil {
t.Fatalf("unexpected error while creating verifier: %v", err) t.Fatalf("unexpected error while creating verifier: %v", err)
} }
@ -1509,7 +1569,7 @@ func verifyResult(outcome *notation.VerificationOutcome, expectedResult notation
} }
} }
// testTrustStore implements truststore.X509TrustStore and returns the trusted certificates for a given trust-store. // testTrustStore implements [truststore.X509TrustStore] and returns the trusted certificates for a given trust-store.
type testTrustStore struct{} type testTrustStore struct{}
func (ts *testTrustStore) GetCertificates(_ context.Context, _ truststore.Type, _ string) ([]*x509.Certificate, error) { func (ts *testTrustStore) GetCertificates(_ context.Context, _ truststore.Type, _ string) ([]*x509.Certificate, error) {