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:
default:
target: 80%
patch:
default:
target: 80%

View File

@ -1,3 +1,3 @@
# Repo-Level Owners (in alphabetical order)
# 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)
# Note: This is for the notaryproject/notation-go repo
Junjie Gao <junjiegao@microsoft.com> (@JeyJeyGao)
Milind Gokarn <gokarnm@amazon.com> (@gokarnm)
Patrick Zheng <patrickzheng@microsoft.com> (@Two-Hearts)
Rakesh Gariganti <garigant@amazon.com> (@rgnote)
@ -17,3 +16,6 @@ Rakesh Gariganti <garigant@amazon.com> (@rgnote)
# Emeritus Org Maintainers (in alphabetical order)
Justin Cormack <justin.cormack@docker.com> (@justincormack)
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
check-line-endings: ## check line endings
! 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
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)
[![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)
[![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.

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
}
var errorKeyNameEmpty = errors.New("key name cannot be empty")
var errKeyNotFound = errors.New("signing key not found")
// SigningKeys reflects the signingkeys.json file.
type SigningKeys struct {
Default *string `json:"default,omitempty"`
@ -67,13 +64,12 @@ func NewSigningKeys() *SigningKeys {
// Add adds new signing key
func (s *SigningKeys) Add(name, keyPath, certPath string, markDefault bool) error {
if name == "" {
return errorKeyNameEmpty
return ErrKeyNameEmpty
}
_, err := tls.LoadX509KeyPair(certPath, keyPath)
if err != nil {
return err
}
ks := KeySuite{
Name: name,
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 {
logger := log.GetLogger(ctx)
logger.Debugf("Adding key with name %v and plugin name %v", keyName, pluginName)
if keyName == "" {
return errorKeyNameEmpty
return ErrKeyNameEmpty
}
if id == "" {
return errors.New("missing key id")
}
if pluginName == "" {
return errors.New("plugin name cannot be empty")
}
mgr := plugin.NewCLIManager(dir.PluginFS())
_, err := mgr.Get(ctx, pluginName)
if err != nil {
return err
}
ks := KeySuite{
Name: keyName,
ExternalKey: &ExternalKey{
@ -115,7 +106,6 @@ func (s *SigningKeys) AddPlugin(ctx context.Context, keyName, id, pluginName str
PluginConfig: pluginConfig,
},
}
if err = s.add(ks, markDefault); err != nil {
logger.Error("Failed to add key with error: %v", 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
func (s *SigningKeys) Get(keyName string) (KeySuite, error) {
if keyName == "" {
return KeySuite{}, errorKeyNameEmpty
return KeySuite{}, ErrKeyNameEmpty
}
idx := slices.IndexIsser(s.Keys, keyName)
if idx < 0 {
return KeySuite{}, errKeyNotFound
return KeySuite{}, KeyNotFoundError{KeyName: keyName}
}
return s.Keys[idx], nil
}
@ -144,7 +132,6 @@ func (s *SigningKeys) GetDefault() (KeySuite, error) {
return KeySuite{}, errors.New("default signing key not set." +
" Please set default signing key or specify a key name")
}
return s.Get(*s.Default)
}
@ -153,12 +140,11 @@ func (s *SigningKeys) Remove(keyName ...string) ([]string, error) {
var deletedNames []string
for _, name := range keyName {
if name == "" {
return deletedNames, errorKeyNameEmpty
return deletedNames, ErrKeyNameEmpty
}
idx := slices.IndexIsser(s.Keys, name)
if idx < 0 {
return deletedNames, errors.New(name + ": not found")
return deletedNames, KeyNotFoundError{KeyName: name}
}
s.Keys = slices.Delete(s.Keys, idx)
deletedNames = append(deletedNames, name)
@ -172,13 +158,11 @@ func (s *SigningKeys) Remove(keyName ...string) ([]string, error) {
// UpdateDefault updates default signing key
func (s *SigningKeys) UpdateDefault(keyName string) error {
if keyName == "" {
return errorKeyNameEmpty
return ErrKeyNameEmpty
}
if !slices.ContainsIsser(s.Keys, keyName) {
return fmt.Errorf("key with name '%s' not found", keyName)
return KeyNotFoundError{KeyName: keyName}
}
s.Default = &keyName
return nil
}
@ -189,11 +173,9 @@ func (s *SigningKeys) Save() error {
if err != nil {
return err
}
if err := validateKeys(s); err != nil {
return err
}
return save(path, s)
}
@ -208,11 +190,9 @@ func LoadSigningKeys() (*SigningKeys, error) {
}
return nil, err
}
if err := validateKeys(&config); err != nil {
return nil, err
}
return &config, nil
}
@ -224,11 +204,9 @@ func LoadExecSaveSigningKeys(fn func(keys *SigningKeys) error) error {
if err != nil {
return err
}
if err := fn(signingKeys); err != nil {
return err
}
return signingKeys.Save()
}
@ -241,12 +219,10 @@ func (s *SigningKeys) add(key KeySuite, markDefault bool) error {
if slices.ContainsIsser(s.Keys, key.Name) {
return fmt.Errorf("signing key with name %q already exists", key.Name)
}
s.Keys = append(s.Keys, key)
if markDefault {
s.Default = &key.Name
}
return nil
}
@ -262,17 +238,14 @@ func validateKeys(config *SigningKeys) error {
}
uniqueKeyNames.Add(key.Name)
}
if config.Default != nil {
defaultKey := *config.Default
if len(defaultKey) == 0 {
return fmt.Errorf("malformed %s: default key name cannot be empty", dir.PathSigningKeys)
}
if !uniqueKeyNames.Contains(defaultKey) {
return fmt.Errorf("malformed %s: default key '%s' not found", dir.PathSigningKeys, defaultKey)
}
}
return nil
}

View File

@ -17,6 +17,7 @@ import (
"context"
"crypto/x509"
"encoding/pem"
"errors"
"os"
"path/filepath"
"reflect"
@ -310,14 +311,22 @@ func TestGet(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")
}
if !errors.Is(err, KeyNotFoundError{KeyName: "nonExistent"}) {
t.Error("expected Get() to return ErrorKeyNotFound")
}
})
t.Run("InvalidName", func(t *testing.T) {
if _, err := sampleSigningKeysInfo.Get(""); err == nil {
t.Error("expected Get() to fail for invalid key name")
t.Run("EmptyName", func(t *testing.T) {
_, err := sampleSigningKeysInfo.Get("")
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) {
if err := sampleSigningKeysInfo.UpdateDefault("nonExistent"); err == nil {
err := sampleSigningKeysInfo.UpdateDefault("nonExistent")
if err == nil {
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) {
if err := sampleSigningKeysInfo.UpdateDefault(""); err == nil {
t.Error("expected Get() to fail for invalid key name")
t.Run("EmptyName", func(t *testing.T) {
err := sampleSigningKeysInfo.UpdateDefault("")
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 {
t.Error("Delete() filed to delete key")
}
if keys[0] != testKeyName {
t.Error("Delete() deleted key name mismatch")
}
})
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")
}
if !errors.Is(err, KeyNotFoundError{KeyName: "nonExistent"}) {
t.Error("expected Get() to return ErrorKeyNotFound")
}
})
t.Run("InvalidName", func(t *testing.T) {
if _, err := testSigningKeysInfo.Remove(""); err == nil {
t.Error("expected Get() to fail for invalid key name")
t.Run("EmptyName", func(t *testing.T) {
_, err := testSigningKeysInfo.Remove("")
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 = "signingkeys.json"
// PathTrustPolicy is the OCI trust policy file relative path.
//
// Deprecated: PathTrustPolicy exists for historical compatibility and should not be used.
// To get OCI trust policy path, use PathOCITrustPolicy.
PathTrustPolicy = "trustpolicy.json"

View File

@ -15,11 +15,17 @@ package notation
// ErrorPushSignatureFailed is used when failed to push signature to the
// 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
}
func (e ErrorPushSignatureFailed) Error() string {
func (e PushSignatureFailedError) Error() string {
if 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
// 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
}
func (e ErrorVerificationInconclusive) Error() string {
func (e VerificationInconclusiveError) Error() string {
if e.Msg != "" {
return e.Msg
}
@ -41,11 +53,17 @@ func (e ErrorVerificationInconclusive) Error() string {
// ErrorNoApplicableTrustPolicy is used when there is no trust policy that
// 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
}
func (e ErrorNoApplicableTrustPolicy) Error() string {
func (e NoApplicableTrustPolicyError) Error() string {
if e.Msg != "" {
return e.Msg
}
@ -54,11 +72,17 @@ func (e ErrorNoApplicableTrustPolicy) Error() string {
// ErrorSignatureRetrievalFailed is used when notation is unable to retrieve the
// 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
}
func (e ErrorSignatureRetrievalFailed) Error() string {
func (e SignatureRetrievalFailedError) Error() string {
if e.Msg != "" {
return e.Msg
}
@ -67,11 +91,17 @@ func (e ErrorSignatureRetrievalFailed) Error() string {
// ErrorVerificationFailed is used when it is determined that the digital
// 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
}
func (e ErrorVerificationFailed) Error() string {
func (e VerificationFailedError) Error() string {
if e.Msg != "" {
return e.Msg
}
@ -80,11 +110,17 @@ func (e ErrorVerificationFailed) Error() string {
// ErrorUserMetadataVerificationFailed is used when the signature does not
// 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
}
func (e ErrorUserMetadataVerificationFailed) Error() string {
func (e UserMetadataVerificationFailedError) Error() string {
if 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.
// Users should replace `exampleCertTuple.PrivateKey` with their own private
// 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
exampleSigner, err := signer.NewGenericSigner(exampleCertTuple.PrivateKey, exampleCerts)
if err != nil {

View File

@ -32,9 +32,9 @@ import (
// examplePolicyDocument is an example of a valid trust policy document.
// trust policy document should follow this spec:
// 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",
TrustPolicies: []trustpolicy.TrustPolicy{
TrustPolicies: []trustpolicy.OCITrustPolicy{
{
Name: "test-statement-name",
RegistryScopes: []string{"example/software"},
@ -73,8 +73,8 @@ func Example_localVerify() {
}
// createTrustStore creates a trust store directory for demo purpose.
// Users could use the default trust store from Notary and add trusted
// certificates into it following the trust store spec:
// Users could use the default trust store from Notary Project and
// add trusted certificates into it following the trust store spec:
// https://github.com/notaryproject/notaryproject/blob/v1.0.0/specs/trust-store-trust-policy.md#trust-store
if err := createTrustStore(); err != nil {
panic(err) // Handle error
@ -172,7 +172,7 @@ func createTrustStore() error {
// generate the `exampleSignatureEnvelopePem` above.)
// Users should replace `exampleX509Certificate` with their own trusted
// 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
exampleX509Certificate := `-----BEGIN CERTIFICATE-----
MIIDQDCCAiigAwIBAgIBUTANBgkqhkiG9w0BAQsFADBOMQswCQYDVQQGEwJVUzEL

View File

@ -45,7 +45,7 @@ func Example_remoteSign() {
// exampleSigner is a notation.Signer given key and X509 certificate chain.
// Users should replace `exampleCertTuple.PrivateKey` with their own private
// 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
exampleSigner, err := signer.NewGenericSigner(exampleCertTuple.PrivateKey, exampleCerts)
if err != nil {
@ -70,13 +70,16 @@ func Example_remoteSign() {
// remote sign core process
// upon successful signing, descriptor of the sign content is returned and
// 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 {
panic(err) // Handle error
}
fmt.Println("Successfully signed")
fmt.Println("targetDesc MediaType:", targetDesc.MediaType)
fmt.Println("targetDesc Digest:", targetDesc.Digest)
fmt.Println("targetDesc Size:", targetDesc.Size)
fmt.Println("targetManifestDesc.MediaType:", targetManifestDesc.MediaType)
fmt.Println("targetManifestDesc.Digest:", targetManifestDesc.Digest)
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.
// trust policy document should follow this spec:
// 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",
TrustPolicies: []trustpolicy.TrustPolicy{
TrustPolicies: []trustpolicy.OCITrustPolicy{
{
Name: "test-statement-name",
RegistryScopes: []string{"*"},
@ -101,7 +101,7 @@ func generateTrustStore() error {
// an example of a valid X509 self-signed certificate for demo purpose ONLY.
// Users should replace `exampleX509Certificate` with their own trusted
// 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
exampleX509Certificate := `-----BEGIN CERTIFICATE-----
MIIDQDCCAiigAwIBAgIBUTANBgkqhkiG9w0BAQsFADBOMQswCQYDVQQGEwJVUzEL

View File

@ -24,14 +24,16 @@ import (
"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() {
//exampleSigner implements notation.Signer and notation.BlobSigner. Given key and X509 certificate chain,
// it provides method to sign OCI artifacts or blobs.
// exampleSigner implements [notation.Signer] and [notation.BlobSigner].
// Given key and X509 certificate chain, it provides method to sign OCI
// artifacts or blobs.
// Users should replace `exampleCertTuple.PrivateKey` with their own private
// key and replace `exampleCerts` with the corresponding certificate chain,
//following the Notary certificate requirements:
// https://github.com/notaryproject/notaryproject/blob/v1.0.0/specs/signature-specification.md#certificate-requirements
// following the Notary Project certificate requirements:
// https://github.com/notaryproject/specifications/tree/9c81dc773508dedc5a81c02c8d805de04f65050b/specs/signature-specification.md#certificate-requirements
exampleSigner, err := signer.NewGenericSigner(exampleCertTuple.PrivateKey, exampleCerts)
if err != nil {
panic(err) // Handle error
@ -42,7 +44,7 @@ func Example_signBlob() {
exampleSignatureMediaType := jws.MediaTypeEnvelope
exampleContentMediaType := "video/mp4"
// exampleSignOptions is an example of notation.SignBlobOptions.
// exampleSignOptions is an example of [notation.SignBlobOptions].
exampleSignOptions := notation.SignBlobOptions{
SignerSignOptions: notation.SignerSignOptions{
SignatureMediaType: exampleSignatureMediaType,
@ -52,7 +54,8 @@ func Example_signBlob() {
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")
// Upon successful signing, signature envelope and signerInfo are returned.

View File

@ -21,6 +21,8 @@ import (
"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-go"
"github.com/notaryproject/notation-go/registry"
@ -43,7 +45,7 @@ func Example_signWithTimestamp() {
// exampleSigner is a notation.Signer given key and X509 certificate chain.
// Users should replace `exampleCertTuple.PrivateKey` with their own private
// 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
exampleSigner, err := signer.NewGenericSigner(exampleCertTuple.PrivateKey, exampleCerts)
if err != nil {
@ -77,23 +79,35 @@ func Example_signWithTimestamp() {
tsaRootCAs := x509.NewCertPool()
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 := notation.SignOptions{
SignerSignOptions: notation.SignerSignOptions{
SignatureMediaType: exampleSignatureMediaType,
Timestamper: httpTimestamper,
TSARootCAs: tsaRootCAs,
TSARevocationValidator: tsaRevocationValidator,
},
ArtifactReference: exampleArtifactReference,
}
targetDesc, err := notation.Sign(context.Background(), exampleSigner, exampleRepo, exampleSignOptions)
targetManifestDesc, sigManifestDesc, err := notation.SignOCI(context.Background(), exampleSigner, exampleRepo, exampleSignOptions)
if err != nil {
panic(err) // Handle error
}
fmt.Println("Successfully signed")
fmt.Println("targetDesc MediaType:", targetDesc.MediaType)
fmt.Println("targetDesc Digest:", targetDesc.Digest)
fmt.Println("targetDesc Size:", targetDesc.Size)
fmt.Println("targetManifestDesc.MediaType:", targetManifestDesc.MediaType)
fmt.Println("targetManifestDesc.Digest:", targetManifestDesc.Digest)
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"
)
// examplePolicyDocument is an example of a valid trust policy document.
// trust policy document should follow this spec:
// https://github.com/notaryproject/notaryproject/blob/v1.1.0/specs/trust-store-trust-policy.md#trust-policy
// exampleBlobPolicyDocument is an example of a valid blob trust policy document.
// blob trust policy document should follow this spec:
// https://github.com/notaryproject/specifications/tree/9c81dc773508dedc5a81c02c8d805de04f65050b/specs/trust-store-trust-policy.md#blob-trust-policy
var exampleBlobPolicyDocument = trustpolicy.BlobDocument{
Version: "1.0",
TrustPolicies: []trustpolicy.BlobTrustPolicy{
@ -42,8 +42,8 @@ var exampleBlobPolicyDocument = trustpolicy.BlobDocument{
},
}
// ExampleVerifyBlob demonstrates how to use verifier.Verify to verify a
// signature of the blob.
// ExampleVerifyBlob demonstrates how to use [notation.VerifyBlob] to verify a
// signature of an arbitrary blob.
func Example_verifyBlob() {
// Both COSE ("application/cose") and JWS ("application/jose+json")
// signature mediaTypes are supported.
@ -53,23 +53,26 @@ func Example_verifyBlob() {
exampleSignatureEnvelope := getSignatureEnvelope()
// 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:
// 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 {
panic(err) // Handle error
}
// exampleVerifier implements notation.Verify and notation.VerifyBlob.
exampleVerifier, err := verifier.NewVerifier(nil, &exampleBlobPolicyDocument, truststore.NewX509TrustStore(dir.ConfigFS()), nil)
// exampleVerifier implements [notation.Verify] and [notation.VerifyBlob].
exampleVerifier, err := verifier.NewVerifierWithOptions(truststore.NewX509TrustStore(dir.ConfigFS()), verifier.VerifierOptions{
BlobTrustPolicy: &exampleBlobPolicyDocument,
})
if err != nil {
panic(err) // Handle error
}
// exampleReader reads the data that needs to be verified. This data can be in a file or in memory.
// exampleReader reads the data that needs to be verified.
// This data can be in a file or in memory.
exampleReader := strings.NewReader("example blob")
// exampleVerifyOptions is an example of notation.VerifierVerifyOptions
// exampleVerifyOptions is an example of [notation.VerifyBlobOptions]
exampleVerifyOptions := notation.VerifyBlobOptions{
BlobVerifierVerifyOptions: notation.BlobVerifierVerifyOptions{
SignatureMediaType: exampleSignatureMediaType,
@ -110,8 +113,8 @@ func createTrustStoreForBlobVerify() error {
// generate the `exampleSignatureEnvelopePem` above.)
// Users should replace `exampleX509Certificate` with their own trusted
// certificate and add to the trust store, following the
// Notary certificate requirements:
// https://github.com/notaryproject/notaryproject/blob/v1.0.0/specs/signature-specification.md#certificate-requirements
// Notary Project certificate requirements:
// https://github.com/notaryproject/specifications/tree/9c81dc773508dedc5a81c02c8d805de04f65050b/specs/signature-specification.md#certificate-requirements
exampleX509Certificate := `-----BEGIN CERTIFICATE-----
MIIEbDCCAtSgAwIBAgIBUzANBgkqhkiG9w0BAQsFADBkMQswCQYDVQQGEwJVUzEL
MAkGA1UECBMCV0ExEDAOBgNVBAcTB1NlYXR0bGUxDzANBgNVBAoTBk5vdGFyeTEl

View File

@ -39,9 +39,9 @@ func Example_verifyWithTimestamp() {
// timestamping configurations.
// trust policy document should follow this spec:
// 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",
TrustPolicies: []trustpolicy.TrustPolicy{
TrustPolicies: []trustpolicy.OCITrustPolicy{
{
Name: "test-statement-name",
RegistryScopes: []string{"*"},
@ -115,7 +115,7 @@ func generateTrustStoreWithTimestamp() error {
// an example of a valid X509 self-signed certificate for demo purpose ONLY.
// Users should replace `exampleX509Certificate` with their own trusted
// 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
exampleX509Certificate := `-----BEGIN CERTIFICATE-----
MIIDQDCCAiigAwIBAgIBUTANBgkqhkiG9w0BAQsFADBOMQswCQYDVQQGEwJVUzEL
@ -149,7 +149,7 @@ GLAfj/jSf9OH9VLTPHOS8/N0Ka4=
// an example of a valid TSA root certificate for demo purpose ONLY.
// Users should replace `exampleTSARootCertificate` with their own trusted
// 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
exampleTSARootCertificate := `-----BEGIN CERTIFICATE-----
MIIFkDCCA3igAwIBAgIQBZsbV56OITLiOQe9p3d1XDANBgkqhkiG9w0BAQwFADBi

26
go.mod
View File

@ -1,26 +1,26 @@
module github.com/notaryproject/notation-go
go 1.22.0
go 1.23.0
require (
github.com/go-ldap/ldap/v3 v3.4.8
github.com/notaryproject/notation-core-go v1.1.1-0.20240920045731-0786f51de737
github.com/go-ldap/ldap/v3 v3.4.11
github.com/notaryproject/notation-core-go v1.3.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/image-spec v1.1.0
github.com/veraison/go-cose v1.1.0
golang.org/x/crypto v0.27.0
golang.org/x/mod v0.21.0
oras.land/oras-go/v2 v2.5.0
github.com/opencontainers/image-spec v1.1.1
github.com/veraison/go-cose v1.3.0
golang.org/x/crypto v0.39.0
golang.org/x/mod v0.25.0
oras.land/oras-go/v2 v2.6.0
)
require (
github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 // indirect
github.com/fxamacker/cbor/v2 v2.7.0 // indirect
github.com/go-asn1-ber/asn1-ber v1.5.5 // indirect
github.com/golang-jwt/jwt/v4 v4.5.0 // indirect
github.com/fxamacker/cbor/v2 v2.8.0 // indirect
github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667 // indirect
github.com/golang-jwt/jwt/v4 v4.5.2 // indirect
github.com/google/uuid v1.6.0 // 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/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa h1:LHTHcTQiSGT7VVbI0o4wBRNQIgn917usHWOd6VAffYI=
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/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E=
github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ=
github.com/go-asn1-ber/asn1-ber v1.5.5 h1:MNHlNMBDgEKD4TcKr36vQN68BA00aDfjIt3/bD50WnA=
github.com/go-asn1-ber/asn1-ber v1.5.5/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0=
github.com/go-ldap/ldap/v3 v3.4.8 h1:loKJyspcRezt2Q3ZRMq2p/0v8iOurlmeXDPw6fikSvQ=
github.com/go-ldap/ldap/v3 v3.4.8/go.mod h1:qS3Sjlu76eHfHGpUdWkAXQTw4beih+cHsco2jXlIXrk=
github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg=
github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
github.com/fxamacker/cbor/v2 v2.8.0 h1:fFtUGXUzXPHTIUdne5+zzMPTfffl3RD5qYnkY40vtxU=
github.com/fxamacker/cbor/v2 v2.8.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ=
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.8-0.20250403174932-29230038a667/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0=
github.com/go-ldap/ldap/v3 v3.4.11 h1:4k0Yxweg+a3OyBLjdYn5OKglv18JNvfDykSoI8bW0gU=
github.com/go-ldap/ldap/v3 v3.4.11/go.mod h1:bY7t0FLK8OAVpp/vV6sSlpz3EQDGcQwc8pF0ujLgKvM=
github.com/golang-jwt/jwt/v4 v4.5.2 h1:YtQM7lnr8iZ+j5q71MGKkNw9Mn7AjHM68uc9g5fXeUI=
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/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM=
github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8=
github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/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/rpc/v2 v2.0.3 h1:7FXXj8Ti1IaVFpSAziCZWNzbNuZmnvw/i6CqLNdWfZY=
github.com/jcmturner/rpc/v2 v2.0.3/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJki3ELg/Hc=
github.com/notaryproject/notation-core-go v1.1.1-0.20240920045731-0786f51de737 h1:Hp93KBCABE29+6zdS0GTg0T1SXj6qGatJyN1JMvTQqk=
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 h1:mWJaw1QBpBxpjLSiKOjzbZvB+xh2Abzk14FHWQ+9Kfs=
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/go.mod h1:RqWSrTOtEASCrGOEffq0n8pSg2KOgKYiWqFWczRSics=
github.com/notaryproject/tspclient-go v0.2.0 h1:g/KpQGmyk/h7j60irIRG1mfWnibNOzJ8WhLqAzuiQAQ=
github.com/notaryproject/tspclient-go v0.2.0/go.mod h1:LGyA/6Kwd2FlM0uk8Vc5il3j0CddbWSHBj/4kxQDbjs=
github.com/notaryproject/tspclient-go v1.0.0 h1:AwQ4x0gX8IHnyiZB1tggpn5NFqHpTEm1SDX8YNv4Dg4=
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/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug=
github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM=
github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040=
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/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/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/veraison/go-cose v1.1.0 h1:AalPS4VGiKavpAzIlBjrn7bhqXiXi4jbMYY/2+UC+4o=
github.com/veraison/go-cose v1.1.0/go.mod h1:7ziE85vSq4ScFTg6wyoMXjucIGOf4JkFEZi/an96Ct4=
github.com/veraison/go-cose v1.3.0 h1:2/H5w8kdSpQJyVtIhx8gmwPJ2uSz1PkyWFx0idbd7rk=
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/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A=
golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70=
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=
golang.org/x/crypto v0.39.0 h1:SHs+kF4LP+f+p14esP5jAoDpHU8Gu/v9lFRK6IT5imM=
golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U=
golang.org/x/mod v0.25.0 h1:n7a+ZbQKQA/Ysbyb0/6IbB1H/X41mKgbhfv7AfG/44w=
golang.org/x/mod v0.25.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=
golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8=
golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
golang.org/x/sync v0.14.0 h1:woo0S4Yywslg6hp4eUFjTVOyKt0RookbpAHG4c1HmhQ=
golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
oras.land/oras-go/v2 v2.5.0 h1:o8Me9kLY74Vp5uw07QXPiitjsw7qNXi8Twd+19Zf02c=
oras.land/oras-go/v2 v2.5.0/go.mod h1:z4eisnLP530vwIOUOJeBIj0aGI0L1C3d53atvCBqZHg=
oras.land/oras-go/v2 v2.6.0 h1:X4ELRsiGkrbeox69+9tzTu492FMUu7zJQW6eJU+I2oc=
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.
// 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 {
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 {
t.Fatal(err)
}
if err := WriteFile(filename, data); err != nil {
if err := WriteFile(tempDir, filename, data); err != nil {
t.Fatal(err)
}
@ -52,7 +52,7 @@ func TestCopyToDir(t *testing.T) {
if err := os.MkdirAll(filepath.Dir(filename), 0700); err != nil {
t.Fatal(err)
}
if err := WriteFile(filename, data); err != nil {
if err := WriteFile(tempDir, filename, data); err != nil {
t.Fatal(err)
}
@ -87,7 +87,7 @@ func TestCopyToDir(t *testing.T) {
if err := os.MkdirAll(filepath.Dir(filename), 0700); err != nil {
t.Fatal(err)
}
if err := WriteFile(filename, data); err != nil {
if err := WriteFile(tempDir, filename, data); err != nil {
t.Fatal(err)
}
// forbid reading
@ -113,7 +113,7 @@ func TestCopyToDir(t *testing.T) {
if err := os.MkdirAll(filepath.Dir(filename), 0700); err != nil {
t.Fatal(err)
}
if err := WriteFile(filename, data); err != nil {
if err := WriteFile(tempDir, filename, data); err != nil {
t.Fatal(err)
}
// forbid dest directory operation
@ -139,7 +139,7 @@ func TestCopyToDir(t *testing.T) {
if err := os.MkdirAll(filepath.Dir(filename), 0700); err != nil {
t.Fatal(err)
}
if err := WriteFile(filename, data); err != nil {
if err := WriteFile(tempDir, filename, data); err != nil {
t.Fatal(err)
}
// forbid writing to destTempDir
@ -159,7 +159,7 @@ func TestCopyToDir(t *testing.T) {
if err := os.MkdirAll(filepath.Dir(filename), 0700); err != nil {
t.Fatal(err)
}
if err := WriteFile(filename, data); err != nil {
if err := WriteFile(tempDir, filename, data); err != nil {
t.Fatal(err)
}
@ -192,7 +192,7 @@ func TestWriteFile(t *testing.T) {
if err != nil {
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") {
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.
// Package notation provides signer and verifier for notation Sign
// and Verification.
// and Verification. It supports both OCI artifact and arbitrary blob.
package notation
import (
@ -31,6 +31,7 @@ import (
orasRegistry "oras.land/oras-go/v2/registry"
"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/cose"
"github.com/notaryproject/notation-core-go/signature/jws"
@ -47,7 +48,7 @@ var errDoneVerification = errors.New("done verification")
var reservedAnnotationPrefixes = [...]string{"io.cncf.notary"}
// SignerSignOptions contains parameters for Signer.Sign.
// SignerSignOptions contains parameters for [Signer] and [BlobSigner].
type SignerSignOptions struct {
// SignatureMediaType is the envelope type of the signature.
// 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 *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.
@ -80,18 +86,21 @@ type Signer interface {
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 {
SignerSignOptions
// ContentMediaType is the media-type of the blob being signed.
ContentMediaType string
// UserMetadata contains key-value pairs that are added to the signature
// payload
UserMetadata map[string]string
}
// BlobDescriptorGenerator creates descriptor using the digest Algorithm.
// Below is the example of minimal descriptor, it must contain mediatype, digest and size of the artifact
// Below is the example of minimal descriptor, it must contain mediatype,
// digest and size of the artifact.
//
// {
// "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,
// and packing in various signature formats.
type BlobSigner interface {
// SignBlob signs the descriptor returned by genDesc ,
// and returns the signature and SignerInfo
// SignBlob signs the descriptor returned by genDesc, and returns the
// signature and SignerInfo.
SignBlob(ctx context.Context, genDesc BlobDescriptorGenerator, opts SignerSignOptions) ([]byte, *signature.SignerInfo, error)
}
@ -116,7 +125,7 @@ type signerAnnotation interface {
PluginAnnotations() map[string]string
}
// SignOptions contains parameters for notation.Sign.
// SignOptions contains parameters for [notation.Sign].
type SignOptions struct {
SignerSignOptions
@ -131,13 +140,30 @@ type SignOptions struct {
// Sign signs the OCI artifact and push the signature to the Repository.
// 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) {
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
if err := validateSignArguments(signer, signOpts.SignerSignOptions); err != nil {
return ocispec.Descriptor{}, err
return ocispec.Descriptor{}, ocispec.Descriptor{}, err
}
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)
@ -146,69 +172,70 @@ func Sign(ctx context.Context, signer Signer, repo registry.Repository, signOpts
// artifactRef is a valid full reference
artifactRef = ref.Reference
}
targetDesc, err := repo.Resolve(ctx, artifactRef)
artifactManifestDesc, err = repo.Resolve(ctx, artifactRef)
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
// the resolved digest
if artifactRef != targetDesc.Digest.String() {
if artifactRef != artifactManifestDesc.Digest.String() {
if _, err := digest.Parse(artifactRef); err == nil {
// 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
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 {
return ocispec.Descriptor{}, err
return ocispec.Descriptor{}, ocispec.Descriptor{}, err
}
sig, signerInfo, err := signer.Sign(ctx, descToSign, signOpts.SignerSignOptions)
if err != nil {
return ocispec.Descriptor{}, err
return ocispec.Descriptor{}, ocispec.Descriptor{}, err
}
var pluginAnnotations map[string]string
if signerAnts, ok := signer.(signerAnnotation); ok {
pluginAnnotations = signerAnts.PluginAnnotations()
}
logger.Debug("Generating annotation")
annotations, err := generateAnnotations(signerInfo, pluginAnnotations)
if err != nil {
return ocispec.Descriptor{}, err
return ocispec.Descriptor{}, ocispec.Descriptor{}, err
}
logger.Debugf("Generated annotations: %+v", annotations)
logger.Debugf("Pushing signature of artifact descriptor: %+v, signature media type: %v", targetDesc, signOpts.SignatureMediaType)
_, _, err = repo.PushSignature(ctx, signOpts.SignatureMediaType, sig, targetDesc, annotations)
logger.Debugf("Pushing signature of artifact descriptor: %+v, signature media type: %v", artifactManifestDesc, signOpts.SignatureMediaType)
_, sigManifestDesc, err = repo.PushSignature(ctx, signOpts.SignatureMediaType, sig, artifactManifestDesc, annotations)
if err != nil {
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")
return ocispec.Descriptor{}, ocispec.Descriptor{}, ErrorPushSignatureFailed{Msg: err.Error()}
}
return ocispec.Descriptor{}, ErrorPushSignatureFailed{Msg: err.Error()}
}
return targetDesc, nil
return artifactManifestDesc, sigManifestDesc, nil
}
// SignBlob signs the arbitrary data and returns the signature
// SignBlob signs the arbitrary data from blobReader and returns
// the signature and SignerInfo.
func SignBlob(ctx context.Context, signer BlobSigner, blobReader io.Reader, signBlobOpts SignBlobOptions) ([]byte, *signature.SignerInfo, error) {
// sanity checks
if err := validateSignArguments(signer, signBlobOpts.SignerSignOptions); err != nil {
return nil, nil, err
}
if blobReader == nil {
return nil, nil, errors.New("blobReader cannot be nil")
}
if signBlobOpts.ContentMediaType == "" {
return nil, nil, errors.New("content media-type cannot be empty")
}
if err := validateContentMediaType(signBlobOpts.ContentMediaType); err != nil {
return nil, nil, err
}
@ -233,33 +260,26 @@ func validateSignArguments(signer any, signOpts SignerSignOptions) error {
if err := validateSigMediaType(signOpts.SignatureMediaType); err != nil {
return err
}
return nil
}
func addUserMetadataToDescriptor(ctx context.Context, desc ocispec.Descriptor, userMetadata map[string]string) (ocispec.Descriptor, error) {
logger := log.GetLogger(ctx)
if desc.Annotations == nil && len(userMetadata) > 0 {
desc.Annotations = map[string]string{}
}
for k, v := range userMetadata {
logger.Debugf("Adding metadata %v=%v to annotations", k, v)
for _, reservedPrefix := range reservedAnnotationPrefixes {
if strings.HasPrefix(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 {
return desc, fmt.Errorf("error adding user metadata: metadata key %v is already present in the target artifact", k)
}
desc.Annotations[k] = v
}
return desc, nil
}
@ -301,6 +321,7 @@ type VerificationOutcome struct {
Error error
}
// UserMetadata returns the user metadata from the signature envelope.
func (outcome *VerificationOutcome) UserMetadata() (map[string]string, error) {
if outcome.EnvelopeContent == nil {
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 {
return nil, errors.New("failed to unmarshal the payload content in the signature blob to envelope.Payload")
}
if payload.TargetArtifact.Annotations == nil {
return map[string]string{}, 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 {
// ArtifactReference is the reference of the artifact that is being
// verified against to. It must be a full reference.
@ -338,17 +358,17 @@ type VerifierVerifyOptions struct {
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 {
// Verify verifies the `signature` associated with the target OCI artifact
//with manifest descriptor `desc`, and returns the outcome upon
// with manifest descriptor `desc`, and returns the outcome upon
// successful verification.
// If nil signature is present and the verification level is not 'skip',
// an error will be returned.
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 {
// SignatureMediaType is the envelope type of the signature.
// 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.
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
// returns the outcome upon successful verification.
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)
}
// VerifyOptions contains parameters for notation.Verify.
// VerifyOptions contains parameters for [notation.Verify].
type VerifyOptions struct {
// ArtifactReference is the reference of the artifact that is being
// verified against to.
@ -399,7 +419,7 @@ type VerifyOptions struct {
UserMetadata map[string]string
}
// VerifyBlobOptions contains parameters for notation.VerifyBlob.
// VerifyBlobOptions contains parameters for [notation.VerifyBlob].
type VerifyBlobOptions struct {
BlobVerifierVerifyOptions
@ -408,32 +428,27 @@ type VerifyBlobOptions struct {
}
// VerifyBlob performs signature verification for a blob using notation supported
// verification types (like integrity, authenticity, etc.) and return the
// successful signature verification outcome. The blob is read using blobReader and
// upon successful verification, it returns the descriptor of the blob.
// verification types (like integrity, authenticity, etc.) and returns the
// successful signature verification outcome. The blob is read using blobReader,
// and upon successful verification, it returns the descriptor of the blob.
// For more details on signature verification, see
// https://github.com/notaryproject/notaryproject/blob/main/specs/trust-store-trust-policy.md#signature-verification
func VerifyBlob(ctx context.Context, blobVerifier BlobVerifier, blobReader io.Reader, signature []byte, verifyBlobOpts VerifyBlobOptions) (ocispec.Descriptor, *VerificationOutcome, error) {
if blobVerifier == nil {
return ocispec.Descriptor{}, nil, errors.New("blobVerifier cannot be nil")
}
if blobReader == nil {
return ocispec.Descriptor{}, nil, errors.New("blobReader cannot be nil")
}
if len(signature) == 0 {
return ocispec.Descriptor{}, nil, errors.New("signature cannot be nil or empty")
}
if err := validateContentMediaType(verifyBlobOpts.ContentMediaType); err != nil {
return ocispec.Descriptor{}, nil, err
}
if err := validateSigMediaType(verifyBlobOpts.SignatureMediaType); err != nil {
return ocispec.Descriptor{}, nil, err
}
getDescFunc := getDescriptorFunc(ctx, blobReader, verifyBlobOpts.ContentMediaType, verifyBlobOpts.UserMetadata)
vo, err := blobVerifier.VerifyBlob(ctx, getDescFunc, signature, verifyBlobOpts.BlobVerifierVerifyOptions)
if err != nil {
@ -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 {
return ocispec.Descriptor{}, nil, err
}
return desc, vo, nil
}
// 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.
// For more details on signature verification, see
// 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,
UserMetadata: verifyOpts.UserMetadata,
}
if skipChecker, ok := verifier.(verifySkipper); ok {
logger.Info("Checking whether signature verification should be skipped or not")
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
}
if skip {
logger.Infoln("Verification skipped for", verifyOpts.ArtifactReference)
logger.Infoln("Signature verification skipped for", verifyOpts.ArtifactReference)
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
@ -502,7 +515,7 @@ func Verify(ctx context.Context, verifier Verifier, repo registry.Repository, ve
}
if ref.ValidateReferenceAsDigest() != nil {
// 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")
} 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())}
@ -547,6 +560,7 @@ func Verify(ctx context.Context, verifier Verifier, repo registry.Repository, ve
}
// at this point, the signature is verified successfully
verificationSucceeded = true
// on success, verificationOutcomes only contains the
// succeeded outcome
verificationOutcomes = []*VerificationOutcome{outcome}
@ -555,14 +569,11 @@ func Verify(ctx context.Context, verifier Verifier, repo registry.Repository, ve
// early break on success
return errDoneVerification
}
if numOfSignatureProcessed >= verifyOpts.MaxSignatureAttempts {
return errExceededMaxVerificationLimit
}
return nil
})
if err != nil && !errors.Is(err, errDoneVerification) {
if errors.Is(err, errExceededMaxVerificationLimit) {
return ocispec.Descriptor{}, verificationOutcomes, err

View File

@ -307,8 +307,7 @@ func TestSignOptsUnknownMediaType(t *testing.T) {
func TestRegistryResolveError(t *testing.T) {
repo := mock.NewRepository()
policyDocument := dummyPolicyDocument()
verifier := dummyVerifier{&policyDocument, mock.PluginManager{}, false, *trustpolicy.LevelStrict}
verifier := dummyVerifier{&policyDocument, mock.PluginManager{}, false, *trustpolicy.LevelStrict, false}
errorMessage := "network error"
expectedErr := ErrorSignatureRetrievalFailed{Msg: errorMessage}
@ -326,8 +325,7 @@ func TestRegistryResolveError(t *testing.T) {
func TestVerifyEmptyReference(t *testing.T) {
repo := mock.NewRepository()
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"
expectedErr := ErrorSignatureRetrievalFailed{Msg: errorMessage}
@ -343,7 +341,7 @@ func TestVerifyEmptyReference(t *testing.T) {
func TestVerifyTagReferenceFailed(t *testing.T) {
repo := mock.NewRepository()
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\""
expectedErr := ErrorSignatureRetrievalFailed{Msg: errorMessage}
@ -360,7 +358,7 @@ func TestVerifyDigestNotMatchResolve(t *testing.T) {
repo := mock.NewRepository()
repo.MissMatchDigest = true
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)
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)
expectedErr := fmt.Errorf(errorMessage)
expectedErr := errors.New(errorMessage)
_, err := Sign(context.Background(), &dummySigner{}, repo, signOpts)
if err == nil || err.Error() != errorMessage {
@ -395,7 +393,7 @@ func TestSignDigestNotMatchResolve(t *testing.T) {
func TestSkippedSignatureVerification(t *testing.T) {
repo := mock.NewRepository()
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}
_, outcomes, err := Verify(context.Background(), &verifier, repo, opts)
@ -408,7 +406,7 @@ func TestSkippedSignatureVerification(t *testing.T) {
func TestRegistryNoSignatureManifests(t *testing.T) {
repo := mock.NewRepository()
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)
expectedErr := ErrorSignatureRetrievalFailed{Msg: errorMessage}
@ -425,7 +423,7 @@ func TestRegistryNoSignatureManifests(t *testing.T) {
func TestRegistryFetchSignatureBlobError(t *testing.T) {
repo := mock.NewRepository()
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)
expectedErr := ErrorSignatureRetrievalFailed{Msg: errorMessage}
@ -442,21 +440,35 @@ func TestRegistryFetchSignatureBlobError(t *testing.T) {
func TestVerifyValid(t *testing.T) {
repo := mock.NewRepository()
policyDocument := dummyPolicyDocument()
verifier := dummyVerifier{&policyDocument, mock.PluginManager{}, false, *trustpolicy.LevelStrict}
verifier := dummyVerifier{&policyDocument, mock.PluginManager{}, false, *trustpolicy.LevelStrict, false}
// mock the repository
opts := VerifyOptions{ArtifactReference: mock.SampleArtifactUri, MaxSignatureAttempts: 50}
_, _, err := Verify(context.Background(), &verifier, repo, opts)
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) {
repo := mock.NewRepository()
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)}
// mock the repository
@ -472,7 +484,7 @@ func TestExceededMaxSignatureAttempts(t *testing.T) {
repo := mock.NewRepository()
repo.ExceededNumOfSignatures = true
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)}
@ -489,7 +501,7 @@ func TestVerifyFailed(t *testing.T) {
t.Run("verification error", func(t *testing.T) {
policyDocument := dummyPolicyDocument()
repo := mock.NewRepository()
verifier := dummyVerifier{&policyDocument, mock.PluginManager{}, true, *trustpolicy.LevelStrict}
verifier := dummyVerifier{&policyDocument, mock.PluginManager{}, true, *trustpolicy.LevelStrict, false}
expectedErr := ErrorVerificationFailed{}
// mock the repository
@ -516,7 +528,7 @@ func TestVerifyFailed(t *testing.T) {
t.Run("repo is nil", func(t *testing.T) {
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")
// mock the repository
@ -582,20 +594,20 @@ func TestVerifyBlobValid(t *testing.T) {
_, _, err := VerifyBlob(context.Background(), &dummyVerifier{}, strings.NewReader("some content"), []byte("signature"), opts)
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) {
policyDoc = trustpolicy.Document{
func dummyPolicyDocument() (policyDoc trustpolicy.OCIDocument) {
policyDoc = trustpolicy.OCIDocument{
Version: "1.0",
TrustPolicies: []trustpolicy.TrustPolicy{dummyPolicyStatement()},
TrustPolicies: []trustpolicy.OCITrustPolicy{dummyPolicyStatement()},
}
return
}
func dummyPolicyStatement() (policyStatement trustpolicy.TrustPolicy) {
policyStatement = trustpolicy.TrustPolicy{
func dummyPolicyStatement() (policyStatement trustpolicy.OCITrustPolicy) {
policyStatement = trustpolicy.OCITrustPolicy{
Name: "test-statement-name",
RegistryScopes: []string{"registry.acme-rockets.io/software/net-monitor"},
SignatureVerification: trustpolicy.SignatureVerification{VerificationLevel: "strict"},
@ -605,7 +617,6 @@ func dummyPolicyStatement() (policyStatement trustpolicy.TrustPolicy) {
return
}
type dummySigner struct {
fail bool
}
@ -655,6 +666,7 @@ type dummyVerifier struct {
PluginManager plugin.Manager
FailVerify bool
VerificationLevel trustpolicy.VerificationLevel
SkipVerification bool
}
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
}
func (v *dummyVerifier) SkipVerify(_ context.Context, _ VerifierVerifyOptions) (bool, *trustpolicy.VerificationLevel, error) {
if v.SkipVerification {
return true, nil, nil
}
return false, nil, nil
}
func (v *dummyVerifier) VerifyBlob(_ context.Context, _ BlobDescriptorGenerator, _ []byte, _ BlobVerifierVerifyOptions) (*VerificationOutcome, error) {
if v.FailVerify {
return nil, errors.New("failed verify")
@ -744,7 +763,7 @@ func TestLocalContent(t *testing.T) {
MaxSignatureAttempts: math.MaxInt64,
}
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
_, _, err = Verify(context.Background(), &verifier, repo, verifyOpts)
if err != nil {

View File

@ -35,7 +35,7 @@ type Manager interface {
List(ctx context.Context) ([]string, error)
}
// CLIManager implements Manager
// CLIManager implements [Manager]
type CLIManager struct {
pluginFS dir.SysFS
}

View File

@ -27,35 +27,43 @@ import (
"path/filepath"
"strings"
"github.com/notaryproject/notation-go/internal/io"
"github.com/notaryproject/notation-go/internal/slices"
"github.com/notaryproject/notation-go/log"
"github.com/notaryproject/notation-go/plugin/proto"
"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
// GenericPlugin is the base requirement to be a plugin.
//
// Deprecated: GenericPlugin exists for historical compatibility and should not be used.
// To access GenericPlugin, use the notation-plugin-framework-go's plugin.GenericPlugin type.
type GenericPlugin = plugin.GenericPlugin
// SignPlugin defines the required methods to be a SignPlugin.
//
// Deprecated: SignPlugin exists for historical compatibility and should not be used.
// To access SignPlugin, use the notation-plugin-framework-go's plugin.SignPlugin type.
type SignPlugin = plugin.SignPlugin
// VerifyPlugin defines the required method to be a VerifyPlugin.
//
// Deprecated: VerifyPlugin exists for historical compatibility and should not be used.
// To access VerifyPlugin, use the notation-plugin-framework-go's plugin.VerifyPlugin type.
type VerifyPlugin = plugin.VerifyPlugin
// Plugin defines required methods to be a Plugin.
//
// Deprecated: Plugin exists for historical compatibility and should not be used.
// To access Plugin, use the notation-plugin-framework-go's plugin.Plugin type.
type Plugin = plugin.Plugin
// CLIPlugin implements Plugin interface to CLI plugins.
// CLIPlugin implements [Plugin] interface to CLI plugins.
type CLIPlugin struct {
name string
path string
@ -218,10 +226,15 @@ func (c execCommander) Output(ctx context.Context, name string, command plugin.C
var stdout, stderr bytes.Buffer
cmd := exec.CommandContext(ctx, name, string(command))
cmd.Stdin = bytes.NewReader(req)
cmd.Stderr = &stderr
cmd.Stdout = &stdout
// The limit writer will be handled by the caller in run() by comparing the
// bytes written with the expected length of the bytes.
cmd.Stderr = io.LimitWriter(&stderr, maxPluginOutputSize)
cmd.Stdout = io.LimitWriter(&stdout, maxPluginOutputSize)
err := cmd.Run()
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 stdout.Bytes(), nil, nil

View File

@ -19,9 +19,11 @@ import (
"errors"
"os"
"reflect"
"runtime"
"strconv"
"strings"
"testing"
"time"
"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()
t.Run("plugin directory exists without executable.", func(t *testing.T) {
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.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) {

View File

@ -14,5 +14,5 @@
package registry
// 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"

View File

@ -14,19 +14,17 @@
package registry
import (
"bytes"
"context"
"encoding/json"
"errors"
"fmt"
"os"
"github.com/notaryproject/notation-go/log"
"github.com/notaryproject/notation-go/registry/internal/artifactspec"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
"oras.land/oras-go/v2"
"oras.land/oras-go/v2/content"
"oras.land/oras-go/v2/content/oci"
"oras.land/oras-go/v2/errdef"
"oras.land/oras-go/v2/registry"
)
@ -35,30 +33,17 @@ const (
maxManifestSizeLimit = 4 * 1024 * 1024 // 4 MiB
)
var (
// notationEmptyConfigDesc is the descriptor of an empty notation manifest
// config
// reference: https://github.com/notaryproject/specifications/blob/v1.0.0/specs/signature-specification.md#storage
notationEmptyConfigDesc = ocispec.Descriptor{
MediaType: ArtifactTypeNotation,
Digest: ocispec.DescriptorEmptyJSON.Digest,
Size: ocispec.DescriptorEmptyJSON.Size,
}
// notationEmptyConfigData is the data of an empty notation manifest config
notationEmptyConfigData = ocispec.DescriptorEmptyJSON.Data
)
// RepositoryOptions provides user options when creating a Repository
// RepositoryOptions provides user options when creating a [Repository]
// it is kept for future extensibility
type RepositoryOptions struct{}
// repositoryClient implements Repository
// repositoryClient implements [Repository]
type repositoryClient struct {
oras.GraphTarget
RepositoryOptions
}
// NewRepository returns a new Repository.
// NewRepository returns a new [Repository].
// Known implementations of oras.GraphTarget:
// - [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)
@ -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.
func NewRepositoryWithOptions(target oras.GraphTarget, opts RepositoryOptions) Repository {
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.
func NewOCIRepository(path string, opts RepositoryOptions) (Repository, error) {
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 {
return repo.Referrers(ctx, desc, ArtifactTypeNotation, fn)
}
signatureManifests, err := signatureReferrers(ctx, c.GraphTarget, desc)
if err != nil {
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 {
return nil, ocispec.Descriptor{}, fmt.Errorf("signature blob too large: %d bytes", sigBlobDesc.Size)
}
var fetcher content.Fetcher = c.GraphTarget
if repo, ok := c.GraphTarget.(registry.Repository); ok {
fetcher = repo.Blobs()
@ -179,6 +162,7 @@ func (c *repositoryClient) getSignatureBlobDesc(ctx context.Context, sigManifest
// get the signature blob descriptor from signature manifest
var signatureBlobs []ocispec.Descriptor
// OCI image manifest
if sigManifestDesc.MediaType == ocispec.MediaTypeImageManifest {
var sigManifest ocispec.Manifest
@ -193,55 +177,27 @@ func (c *repositoryClient) getSignatureBlobDesc(ctx context.Context, sigManifest
}
signatureBlobs = sigManifest.Blobs
}
if len(signatureBlobs) != 1 {
return ocispec.Descriptor{}, fmt.Errorf("signature manifest requries exactly one signature envelope blob, got %d", len(signatureBlobs))
}
return signatureBlobs[0], nil
}
// 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) {
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{
Subject: &subject,
ManifestAnnotations: annotations,
Layers: []ocispec.Descriptor{blobDesc},
ConfigDescriptor: &configDesc,
}
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
return oras.PackManifest(ctx, c.GraphTarget, oras.PackManifestVersion1_1, ArtifactTypeNotation, opts)
}
// signatureReferrers returns referrer nodes of desc in target filtered by
// the "application/vnd.cncf.notary.signature" artifact type
func signatureReferrers(ctx context.Context, target content.ReadOnlyGraphStorage, desc ocispec.Descriptor) ([]ocispec.Descriptor, error) {
logger := log.GetLogger(ctx)
var results []ocispec.Descriptor
predecessors, err := target.Predecessors(ctx, desc)
if err != nil {
@ -257,6 +213,7 @@ func signatureReferrers(ctx context.Context, target content.ReadOnlyGraphStorage
if err != nil {
return nil, err
}
var artifact artifactspec.Artifact
if err := json.Unmarshal(fetched, &artifact); err != nil {
return nil, err
@ -264,6 +221,10 @@ func signatureReferrers(ctx context.Context, target content.ReadOnlyGraphStorage
if artifact.Subject == nil || !content.Equal(*artifact.Subject, desc) {
continue
}
if artifact.ArtifactType != ArtifactTypeNotation {
// not a valid Notary Project signature
continue
}
node.ArtifactType = artifact.ArtifactType
node.Annotations = artifact.Annotations
case ocispec.MediaTypeImageManifest:
@ -274,6 +235,7 @@ func signatureReferrers(ctx context.Context, target content.ReadOnlyGraphStorage
if err != nil {
return nil, err
}
var image ocispec.Manifest
if err := json.Unmarshal(fetched, &image); err != nil {
return nil, err
@ -281,15 +243,39 @@ func signatureReferrers(ctx context.Context, target content.ReadOnlyGraphStorage
if image.Subject == nil || !content.Equal(*image.Subject, desc) {
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
} else {
// not a valid Notary Project signature
continue
}
default:
// not a valid Notary Project signature
continue
}
node.Annotations = image.Annotations
default:
continue
}
// only keep nodes of "application/vnd.cncf.notary.signature"
if node.ArtifactType == ArtifactTypeNotation {
// add the node to results
results = append(results, node)
}
}
return results, nil
}

View File

@ -16,6 +16,7 @@ package registry
import (
"bytes"
"context"
"errors"
"fmt"
"io"
"net/http"
@ -140,7 +141,7 @@ func (c mockRemoteClient) Do(req *http.Request) (*http.Response, error) {
Body: io.NopCloser(bytes.NewReader([]byte{})),
}, nil
case "/v2/test/manifests/" + invalidDigest:
return &http.Response{}, fmt.Errorf(errMsg)
return &http.Response{}, errors.New(errMsg)
case "v2/test/manifest/" + validDigest2:
return &http.Response{
StatusCode: http.StatusOK,
@ -164,7 +165,7 @@ func (c mockRemoteClient) Do(req *http.Request) (*http.Response, error) {
},
}, nil
default:
return &http.Response{}, fmt.Errorf(msg)
return &http.Response{}, errors.New(msg)
}
case "/v2/test/referrers/":
return &http.Response{
@ -220,7 +221,7 @@ func (c mockRemoteClient) Do(req *http.Request) (*http.Response, error) {
}
return resp, nil
}
return &http.Response{}, fmt.Errorf(errMsg)
return &http.Response{}, errors.New(errMsg)
}
}
@ -480,8 +481,8 @@ var (
}
expectedSignatureManifestDesc = ocispec.Descriptor{
MediaType: "application/vnd.oci.image.manifest.v1+json",
Digest: "sha256:baeaea44f55c94499b7e082bd3c98ad5ec40fdf23ef89cdf4e5db6b83e4f18f5",
Size: 728,
Digest: "sha256:64300ad03f1dcd18136787363f3069c9598623221cbe76e3233d35266b7973d6",
Size: 793,
}
expectedSignatureBlobDesc = ocispec.Descriptor{
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) {
store := &testStorage{
store: &memory.Store{},
@ -825,7 +851,6 @@ func TestSignatureReferrers(t *testing.T) {
descriptors, err := signatureReferrers(context.Background(), store, ocispec.Descriptor{
Digest: "sha256:44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a",
})
if err != nil {
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.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.
// It implements notation.Signer
//
// It implements [notation.Signer] and [notation.BlobSigner].
type PluginSigner struct {
plugin plugin.SignPlugin
keyID string
@ -49,16 +50,17 @@ var algorithms = map[crypto.Hash]digest.Algorithm{
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,
// 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) {
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,
// 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) {
@ -68,7 +70,6 @@ func NewPluginSigner(plugin plugin.SignPlugin, keyID string, pluginConfig map[st
if keyID == "" {
return nil, errors.New("keyID not specified")
}
return &PluginSigner{
plugin: plugin,
keyID: keyID,
@ -82,24 +83,21 @@ func (s *PluginSigner) PluginAnnotations() map[string]string {
}
// 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) {
logger := log.GetLogger(ctx)
mergedConfig := s.mergeConfig(opts.PluginConfig)
logger.Debug("Invoking plugin's get-plugin-metadata command")
metadata, err := s.plugin.GetMetadata(ctx, &plugin.GetMetadataRequest{PluginConfig: mergedConfig})
if err != nil {
return nil, nil, err
}
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) {
ks, err := s.getKeySpec(ctx, mergedConfig)
if err != nil {
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)
if err != nil {
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 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) {
logger := log.GetLogger(ctx)
mergedConfig := s.mergeConfig(opts.PluginConfig)
logger.Debug("Invoking plugin's get-plugin-metadata command")
metadata, err := s.plugin.GetMetadata(ctx, &plugin.GetMetadataRequest{PluginConfig: mergedConfig})
if err != nil {
return nil, nil, err
}
// only support blob signing with the signature generator capability because
// the envelope generator capability is designed for OCI signing.
// A new capability may be added in the future for blob signing.
if !metadata.HasCapability(plugin.CapabilitySignatureGenerator) {
return nil, nil, fmt.Errorf("the plugin %q lacks the signature generator capability required for blob signing", metadata.Name)
}
logger.Debug("Invoking plugin's describe-key command")
ks, err := s.getKeySpec(ctx, mergedConfig)
@ -138,14 +141,8 @@ func (s *PluginSigner) SignBlob(ctx context.Context, descGenFunc notation.BlobDe
if err != nil {
return nil, nil, err
}
logger.Debugf("Using plugin %v with capabilities %v to sign blob using descriptor %+v", metadata.Name, metadata.Capabilities, desc)
if metadata.HasCapability(plugin.CapabilitySignatureGenerator) {
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) {
@ -155,11 +152,9 @@ func (s *PluginSigner) getKeySpec(ctx context.Context, config map[string]string)
if err != nil {
return signature.KeySpec{}, err
}
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 proto.DecodeKeySpec(descKeyResp.KeySpec)
}
@ -175,7 +170,6 @@ func (s *PluginSigner) generateSignature(ctx context.Context, desc ocispec.Descr
keySpec: ks,
},
}
opts.SigningAgent = fmt.Sprintf("%s %s/%s", signingAgent, metadata.Name, metadata.Version)
return genericSigner.Sign(ctx, desc, opts)
}
@ -188,6 +182,7 @@ func (s *PluginSigner) generateSignatureEnvelope(ctx context.Context, desc ocisp
if err != nil {
return nil, nil, fmt.Errorf("envelope payload can't be marshalled: %w", err)
}
// Execute plugin sign command.
req := &plugin.GenerateEnvelopeRequest{
ContractVersion: plugin.ContractVersion,
@ -210,13 +205,11 @@ func (s *PluginSigner) generateSignatureEnvelope(ctx context.Context, desc ocisp
resp.SignatureEnvelopeType, req.SignatureEnvelopeType,
)
}
logger.Debug("Verifying signature envelope generated by the plugin")
sigEnv, err := signature.ParseEnvelope(opts.SignatureMediaType, resp.SignatureEnvelope)
if err != nil {
return nil, nil, err
}
envContent, err := sigEnv.Verify()
if err != nil {
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 {
return nil, nil, err
}
content := envContent.Payload.Content
var signedPayload envelope.Payload
if err = json.Unmarshal(content, &signedPayload); err != nil {
return nil, nil, fmt.Errorf("signed envelope payload can't be unmarshalled: %w", err)
}
if !isPayloadDescriptorValid(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 {
return nil, nil, fmt.Errorf("during signing, following unknown attributes were added to subject descriptor: %+q", unknownAttributes)
}
s.manifestAnnotations = resp.Annotations
return resp.SignatureEnvelope, &envContent.SignerInfo, nil
}
func (s *PluginSigner) mergeConfig(config map[string]string) map[string]string {
c := make(map[string]string, len(s.pluginConfig)+len(config))
// First clone s.PluginConfig.
for k, v := range s.pluginConfig {
c[k] = v
}
// Then set or override entries from config.
for k, v := range config {
c[k] = v
@ -266,7 +257,6 @@ func (s *PluginSigner) describeKey(ctx context.Context, config map[string]string
if err != nil {
return nil, err
}
return resp, nil
}
@ -276,6 +266,7 @@ func isDescriptorSubset(original, newDesc ocispec.Descriptor) bool {
if !content.Equal(original, newDesc) {
return false
}
// Plugins may append additional annotations but not replace/override
// existing.
for k, v := range original.Annotations {
@ -293,6 +284,7 @@ func isPayloadDescriptorValid(originalDesc, newDesc ocispec.Descriptor) bool {
func areUnknownAttributesAdded(content []byte) []string {
var targetArtifactMap map[string]interface{}
// Ignoring error because we already successfully unmarshalled before this
// point
_ = json.Unmarshal(content, &targetArtifactMap)
@ -349,12 +341,10 @@ func (s *pluginPrimitiveSigner) Sign(payload []byte) ([]byte, []*x509.Certificat
if err != nil {
return nil, nil, err
}
keySpecHash, err := proto.HashAlgorithmFromKeySpec(s.keySpec)
if err != nil {
return nil, nil, err
}
req := &plugin.GenerateSignatureRequest{
ContractVersion: plugin.ContractVersion,
KeyID: s.keyID,
@ -363,7 +353,6 @@ func (s *pluginPrimitiveSigner) Sign(payload []byte) ([]byte, []*x509.Certificat
Payload: payload,
PluginConfig: s.pluginConfig,
}
resp, err := s.plugin.GenerateSignature(s.ctx, req)
if err != nil {
return nil, nil, err

View File

@ -216,6 +216,17 @@ func (p *mockPlugin) GenerateEnvelope(ctx context.Context, req *proto.GenerateEn
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) {
tests := map[string]struct {
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) {
for _, envelopeType := range signature.RegisteredEnvelopeTypes() {
t.Run(fmt.Sprintf("envelopeType=%v", envelopeType), func(t *testing.T) {

View File

@ -12,8 +12,8 @@
// limitations under the License.
// Package signer provides notation signing functionality. It implements the
// notation.Signer interface by providing builtinSigner for local signing and
// PluginSigner for remote signing.
// [notation.Signer] and [notation.BlobSigner] interfaces by providing
// builtinSigner for local signing and [PluginSigner] for remote signing.
package signer
import (
@ -36,19 +36,22 @@ import (
// signingAgent is the unprotected header field used by signature.
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 {
signer signature.Signer
}
// New returns a builtinSigner given key and cert chain
// Deprecated: New function exists for historical compatibility and should not be used.
// To create GenericSigner, use NewGenericSigner() function.
// 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.
func New(key crypto.PrivateKey, certChain []*x509.Certificate) (notation.Signer, error) {
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) {
localSigner, err := signature.NewLocalSigner(certChain, key)
if err != nil {
@ -59,12 +62,13 @@ func NewGenericSigner(key crypto.PrivateKey, certChain []*x509.Certificate) (*Ge
}, 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) {
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) {
if keyPath == "" {
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
// marshalled envelope.
// signature and SignerInfo.
func (s *GenericSigner) Sign(ctx context.Context, desc ocispec.Descriptor, opts notation.SignerSignOptions) ([]byte, *signature.SignerInfo, error) {
logger := log.GetLogger(ctx)
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 {
return nil, nil, fmt.Errorf("envelope payload can't be marshalled: %w", err)
}
var signingAgentId string
if opts.SigningAgent != "" {
signingAgentId = opts.SigningAgent
@ -130,6 +133,7 @@ func (s *GenericSigner) Sign(ctx context.Context, desc ocispec.Descriptor, opts
SigningAgent: signingAgentId,
Timestamper: opts.Timestamper,
TSARootCAs: opts.TSARootCAs,
TSARevocationValidator: opts.TSARevocationValidator,
}
// 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(" SigningScheme: %v", signReq.SigningScheme)
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
signReq = signReq.WithContext(ctx)
@ -152,12 +162,10 @@ func (s *GenericSigner) Sign(ctx context.Context, desc ocispec.Descriptor, opts
if err != nil {
return nil, nil, err
}
sig, err := sigEnv.Sign(signReq)
if err != nil {
return nil, nil, err
}
envContent, err := sigEnv.Verify()
if err != nil {
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
}
// SignBlob signs the descriptor returned by blobGen and returns the marshalled envelope
func (s *GenericSigner) SignBlob(ctx context.Context, descGenFunc notation.BlobDescriptorGenerator, opts notation.SignerSignOptions) ([]byte, *signature.SignerInfo, error) {
// SignBlob signs the descriptor returned by genDesc, and returns the
// signature and SignerInfo.
func (s *GenericSigner) SignBlob(ctx context.Context, genDesc notation.BlobDescriptorGenerator, opts notation.SignerSignOptions) ([]byte, *signature.SignerInfo, error) {
logger := log.GetLogger(ctx)
logger.Debugf("Generic blob signing for signature media type %v", opts.SignatureMediaType)
logger.Debugf("Generic blob signing for signature media type %s", opts.SignatureMediaType)
ks, err := s.signer.KeySpec()
if err != nil {
return nil, nil, err
}
desc, err := getDescriptor(ks, descGenFunc)
desc, err := getDescriptor(ks, genDesc)
if err != nil {
return nil, nil, err
}
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()]
if !ok {
return ocispec.Descriptor{}, fmt.Errorf("unknown hashing algo %v", ks.SignatureAlgorithm().Hash())
}
return descGenFunc(digestAlg)
return genDesc(digestAlg)
}

View File

@ -30,6 +30,8 @@ import (
"testing"
"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/cose"
_ "github.com/notaryproject/notation-core-go/signature/jws"
@ -53,7 +55,8 @@ type keyCertPair struct {
var keyCertPairCollections []*keyCertPair
// setUpKeyCertPairCollections setups all combinations of private key and certificates.
// setUpKeyCertPairCollections setups all combinations of private key and
// certificates.
func setUpKeyCertPairCollections() []*keyCertPair {
// rsa
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)
}
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) {
// sign with key
dir := t.TempDir()
@ -257,6 +271,27 @@ func TestSignWithTimestamping(t *testing.T) {
if err == nil || err.Error() != expectedErrMsg {
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) {

View File

@ -110,11 +110,11 @@ func (c *FileCache) Get(ctx context.Context, url string) (*corecrl.Bundle, error
// check expiry
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 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 {
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 nil

View File

@ -270,7 +270,7 @@ func TestGetFailed(t *testing.T) {
t.Fatal(err)
}
_, 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 {
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)
}
// isCriticalFailure checks whether a VerificationResult fails the entire
// signature verification workflow.
// isCriticalFailure checks whether a [notation.ValidationResult] fails the
// entire signature verification workflow.
// 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.
func isCriticalFailure(result *notation.ValidationResult) bool {
return result.Action == trustpolicy.ActionEnforce && result.Error != nil

View File

@ -216,7 +216,7 @@ func TestAuthenticTimestamp(t *testing.T) {
VerificationLevel: trustpolicy.LevelStrict,
}
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 {
t.Fatalf("expected %s, but got %s", expectedErrMsg, err)
}

View File

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

View File

@ -57,7 +57,7 @@ func TestValidate_BlobDocument_Error(t *testing.T) {
policyDoc := dummyBlobPolicyDocument()
policyDoc.Version = ""
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")
}

View File

@ -25,7 +25,7 @@ import (
"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 {
// Version of the policy document
Version string `json:"version"`
@ -34,7 +34,7 @@ type OCIDocument struct {
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 {
// Name of the policy statement
Name string `json:"name"`
@ -53,28 +53,33 @@ type OCITrustPolicy struct {
}
// 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
// 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
// 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 supportedOCIPolicyVersions = []string{"1.0"}
// LoadOCIDocument retrieves a trust policy document from the local file system.
// It attempts to read from dir.PathOCITrustPolicy first; if not found, it tries dir.PathTrustPolicy.
// If both dir.PathOCITrustPolicy and dir.PathTrustPolicy exist, dir.PathOCITrustPolicy will be read.
// It attempts to read from [dir.PathOCITrustPolicy] first; if not found,
// it tries [dir.PathTrustPolicy].
// If both dir.PathOCITrustPolicy and dir.PathTrustPolicy exist,
// dir.PathOCITrustPolicy will be read.
func LoadOCIDocument() (*OCIDocument, error) {
var doc OCIDocument
// attempt to load the document from dir.PathOCITrustPolicy
if err := getDocument(dir.PathOCITrustPolicy, &doc); err != nil {
// if the document is not found at the first path, try the second path
@ -84,10 +89,10 @@ func LoadOCIDocument() (*OCIDocument, error) {
}
return &doc, nil
}
// if an error occurred other than the document not found, return it
return nil, err
}
return &doc, nil
}
@ -111,18 +116,15 @@ func (policyDoc *OCIDocument) Validate() error {
if len(policyDoc.TrustPolicies) == 0 {
return errors.New("oci trust policy document can not have zero trust policy statements")
}
policyNames := set.New[string]()
for _, statement := range policyDoc.TrustPolicies {
// Verify unique policy statement names across the policy document
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)
}
if err := validatePolicyCore(statement.Name, statement.SignatureVerification, statement.TrustStores, statement.TrustedIdentities); err != nil {
return fmt.Errorf("oci trust policy: %w", err)
}
policyNames.Add(statement.Name)
}
@ -130,14 +132,13 @@ func (policyDoc *OCIDocument) Validate() error {
if err := validateRegistryScopes(policyDoc); err != nil {
return err
}
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
// 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
// policy is found, returns an error.
// see https://github.com/notaryproject/specifications/tree/9c81dc773508dedc5a81c02c8d805de04f65050b/specs/trust-store-trust-policy.md#selecting-a-trust-policy-based-on-artifact-uri
func (policyDoc *OCIDocument) GetApplicableTrustPolicy(artifactReference string) (*OCITrustPolicy, error) {
artifactPath, err := getArtifactPathFromReference(artifactReference)
if err != nil {
@ -155,7 +156,6 @@ func (policyDoc *OCIDocument) GetApplicableTrustPolicy(artifactReference string)
applicablePolicy = (&policyStatement).clone()
}
}
if applicablePolicy != nil {
// a policy with exact match for registry scope takes precedence over
// 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 {
return &OCITrustPolicy{
Name: t.Name,
@ -218,7 +218,6 @@ func getArtifactPathFromReference(artifactReference string) (string, error) {
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)
}
artifactPath := artifactReference[:i]
if err := validateRegistryScopeFormat(artifactPath); err != nil {
return "", err
@ -242,12 +241,10 @@ func validateRegistryScopeFormat(scope string) error {
if len(scope) > 1 && strings.Contains(scope, "*") {
return fmt.Errorf(errorWildCardMessage, scope)
}
domain, repository, found := strings.Cut(scope, "/")
if !found {
return fmt.Errorf(errorMessage, scope)
}
if domain == "" || repository == "" || !domainRegexp.MatchString(domain) || !repositoryRegexp.MatchString(repository) {
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)
}
// GetVerificationLevel returns VerificationLevel struct for the given
// SignatureVerification struct throws error if SignatureVerification is invalid
// GetVerificationLevel returns [VerificationLevel] for the given
// [SignatureVerification] struct.
// It throws error if SignatureVerification is invalid.
func (signatureVerification *SignatureVerification) GetVerificationLevel() (*VerificationLevel, error) {
if signatureVerification.VerificationLevel == "" {
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 {
return nil, fmt.Errorf("invalid signature verification level %q", signatureVerification.VerificationLevel)
}
if len(signatureVerification.Override) == 0 {
// nothing to override, return the base verification level
return baseLevel, nil
}
if baseLevel == LevelSkip {
return nil, fmt.Errorf("signature verification level %q can't be used to customize signature verification", baseLevel.Name)
}
customVerificationLevel := &VerificationLevel{
Name: "custom",
Enforcement: make(map[ValidationType]ValidationAction),
@ -218,13 +216,11 @@ func (signatureVerification *SignatureVerification) GetVerificationLevel() (*Ver
if validationAction == "" {
return nil, fmt.Errorf("verification action %q in custom signature verification is not supported, supported values are %q", value, ValidationActions)
}
if validationType == TypeIntegrity {
return nil, fmt.Errorf("%q verification can not be overridden in custom signature verification", key)
} else if validationType != TypeRevocation && validationAction == ActionSkip {
return nil, fmt.Errorf("%q verification can not be skipped in custom signature verification", key)
}
customVerificationLevel.Enforcement[validationType] = validationAction
}
return customVerificationLevel, nil
@ -244,12 +240,10 @@ func getDocument(path string, v any) error {
}
return err
}
mode := fileInfo.Mode()
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)
}
jsonFile, err := os.Open(path)
if err != nil {
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 nil
}
@ -341,7 +334,6 @@ func validateTrustedIdentities(policyName string, tis []string) error {
if identity == "" {
return fmt.Errorf("trust policy statement %q has an empty trusted identity", policyName)
}
if identity != trustpolicy.Wildcard {
identityPrefix, identityValue, found := strings.Cut(identity, ":")
if !found {
@ -380,12 +372,11 @@ func validateOverlappingDNs(policyName string, parsedDNs []parsedDN) error {
}
}
}
return nil
}
// isValidTrustStoreType returns true if the given string is a valid
// truststore.Type, otherwise false.
// [truststore.Type], otherwise false.
func isValidTrustStoreType(s string) bool {
for _, p := range truststore.Types {
if s == string(p) {

View File

@ -15,6 +15,7 @@
package truststore
import (
"bytes"
"context"
"crypto/x509"
"errors"
@ -29,8 +30,7 @@ import (
"github.com/notaryproject/notation-go/internal/slices"
)
// Type is an enum for trust store types supported such as
// "ca" and "signingAuthority"
// Type is an enum for trust store types supported
type Type string
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 {
// GetCertificates returns certificates under storeType/namedStore
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 {
return &x509TrustStore{trustStorefs}
}
// x509TrustStore implements X509TrustStore
// x509TrustStore implements [X509TrustStore]
type x509TrustStore struct {
trustStorefs dir.SysFS
}
@ -106,6 +106,14 @@ func (trustStore *x509TrustStore) GetCertificates(ctx context.Context, storeType
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)}
}
// 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...)
}
if len(certificates) < 1 {
@ -137,3 +145,14 @@ func ValidateCertificates(certs []*x509.Certificate) error {
func isValidStoreType(storeType Type) bool {
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)
}
}
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
// 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
import (
@ -57,7 +58,8 @@ var algorithms = map[crypto.Hash]digest.Algorithm{
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 {
ociTrustPolicyDoc *trustpolicy.OCIDocument
blobTrustPolicyDoc *trustpolicy.BlobDocument
@ -69,7 +71,7 @@ type verifier struct {
}
// VerifierOptions specifies additional parameters that can be set when using
// the NewVerifierWithOptions constructor
// the [NewVerifierWithOptions] constructor
type VerifierOptions struct {
// RevocationClient is an implementation of revocation.Revocation to use for
// verifying revocation of code signing certificate chain
@ -86,9 +88,18 @@ type VerifierOptions struct {
// RevocationTimestampingValidator is used for verifying revocation of
// timestamping certificate chain with context.
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) {
// load trust policy
policyDocument, err := trustpolicy.LoadOCIDocument()
@ -98,12 +109,15 @@ func NewOCIVerifierFromConfig() (*verifier, error) {
// load trust store
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
func NewBlobVerifierFromConfig() (*verifier, error) {
// load trust policy
// load blob trust policy
policyDocument, err := trustpolicy.LoadBlobDocument()
if err != nil {
return nil, err
@ -111,26 +125,28 @@ func NewBlobVerifierFromConfig() (*verifier, error) {
// load trust store
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,
// pluginManager, and VerifierOptions.
//
// Deprecated: NewWithOptions function exists for historical compatibility and should not be used.
// To create verifier, use NewVerifierWithOptions function.
// Deprecated: NewWithOptions function exists for historical compatibility and
// should not be used. To create verifier, use [NewVerifierWithOptions] function.
func NewWithOptions(ociTrustPolicy *trustpolicy.OCIDocument, trustStore truststore.X509TrustStore, pluginManager plugin.Manager, opts VerifierOptions) (notation.Verifier, error) {
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
func NewVerifier(ociTrustPolicy *trustpolicy.OCIDocument, blobTrustPolicy *trustpolicy.BlobDocument, trustStore truststore.X509TrustStore, pluginManager plugin.Manager) (*verifier, error) {
return NewVerifierWithOptions(ociTrustPolicy, blobTrustPolicy, trustStore, pluginManager, VerifierOptions{})
}
// 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) {
// NewVerifierWithOptions creates a new verifier given trustStore and
// verifierOptions.
func NewVerifierWithOptions(trustStore truststore.X509TrustStore, verifierOptions VerifierOptions) (*verifier, error) {
ociTrustPolicy := verifierOptions.OCITrustPolicy
blobTrustPolicy := verifierOptions.BlobTrustPolicy
if trustStore == nil {
return nil, errors.New("trustStore cannot be nil")
}
@ -151,7 +167,7 @@ func NewVerifierWithOptions(ociTrustPolicy *trustpolicy.OCIDocument, blobTrustPo
ociTrustPolicyDoc: ociTrustPolicy,
blobTrustPolicyDoc: blobTrustPolicy,
trustStore: trustStore,
pluginManager: pluginManager,
pluginManager: verifierOptions.PluginManager,
}
if err := v.setRevocation(verifierOptions); err != nil {
@ -160,20 +176,24 @@ func NewVerifierWithOptions(ociTrustPolicy *trustpolicy.OCIDocument, blobTrustPo
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.
// To create an OCI verifier, use NewOCIVerifierFromConfig function.
// Deprecated: NewFromConfig function exists for historical compatibility and
// should not be used. To create an OCI verifier, use [NewOCIVerifierFromConfig]
// function.
func NewFromConfig() (notation.Verifier, error) {
return NewOCIVerifierFromConfig()
}
// New creates a new verifier given ociTrustPolicy, trustStore and pluginManager.
//
// Deprecated: New function exists for historical compatibility and should not be used.
// To create verifier, use NewVerifier function.
// Deprecated: New function exists for historical compatibility and
// should not be used. To create verifier, use [NewVerifier] function.
func New(ociTrustPolicy *trustpolicy.OCIDocument, trustStore truststore.X509TrustStore, pluginManager plugin.Manager) (notation.Verifier, error) {
return NewVerifier(ociTrustPolicy, nil, trustStore, pluginManager)
return NewVerifierWithOptions(trustStore, VerifierOptions{
OCITrustPolicy: ociTrustPolicy,
PluginManager: pluginManager,
})
}
// setRevocation sets revocation validators of v
@ -238,7 +258,7 @@ func (v *verifier) SkipVerify(ctx context.Context, opts notation.VerifierVerifyO
return false, verificationLevel, nil
}
// VerifyBlob verifies the signature of given blob , and returns the outcome upon
// VerifyBlob verifies the signature of given blob, and returns the outcome upon
// successful verification.
func (v *verifier) VerifyBlob(ctx context.Context, descGenFunc notation.BlobDescriptorGenerator, signature []byte, opts notation.BlobVerifierVerifyOptions) (*notation.VerificationOutcome, error) {
logger := log.GetLogger(ctx)
@ -319,7 +339,7 @@ func (v *verifier) VerifyBlob(ctx context.Context, descGenFunc notation.BlobDesc
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
// successful verification.
// 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
if verificationPluginName != "" {
logger.Debugf("Finding verification plugin %s", verificationPluginName)
logger.Debugf("Finding verification plugin %q", verificationPluginName)
verificationPluginMinVersion, err := getVerificationPluginMinVersion(&outcome.EnvelopeContent.SignerInfo)
if err != nil && err != errExtendedAttributeNotExist {
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]
if certResult.RevocationMethod == revocationresult.RevocationMethodOCSPFallbackCRL {
// log the fallback warning
logger.Warnf("OCSP check failed with unknown error and fallback to CRL check for certificate #%d in chain with subject %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 {
if serverResult.Error != nil {
@ -821,10 +841,10 @@ func revocationFinalResult(certResults []*revocationresult.CertRevocationResult,
// when the final revocation method is OCSPFallbackCRL,
// the OCSP server results should not be logged as an error
// since the CRL revocation check can succeed.
logger.Debugf("Certificate #%d in chain with subject %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
}
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
}
}
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 {
problematicCertSubject = revokedCertSubject
@ -1018,7 +1042,7 @@ func verifyTimestamp(ctx context.Context, policyName string, trustStores []strin
}
// Performing timestamp verification
logger.Info("Performing timestamp verification...")
logger.Debug("Performing timestamp verification...")
// 1. Timestamp countersignature MUST be present
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 {
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
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) {
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
@ -1098,5 +1125,6 @@ func verifyTimestamp(ctx context.Context, policyName string, trustStores []strin
}
// success
logger.Debug("Timestamp verification: Success")
return nil
}

View File

@ -17,10 +17,12 @@ import (
"context"
"crypto/x509"
"crypto/x509/pkix"
"encoding/json"
"encoding/pem"
"errors"
"fmt"
"net/http"
"os"
"path/filepath"
"reflect"
"strconv"
@ -223,7 +225,7 @@ func assertNotationVerification(t *testing.T, scheme signature.SigningScheme) {
for _, level := range verificationLevels {
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
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{
signatureBlob: validSigEnv,
verificationType: trustpolicy.TypeAuthenticity,
@ -728,9 +730,13 @@ func TestNewVerifierWithOptions(t *testing.T) {
if err != nil {
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 {
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")
}
_, err = NewVerifierWithOptions(nil, &blobPolicy, store, pm, opts)
_, err = NewVerifierWithOptions(store, VerifierOptions{
RevocationClient: r,
BlobTrustPolicy: &blobPolicy,
PluginManager: pm,
})
if err != nil {
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 {
t.Fatalf("expected NewVerifierWithOptions constructor to succeed, but got %v", err)
}
opts.RevocationClient = nil
_, err = NewVerifierWithOptions(&ociPolicy, nil, store, pm, opts)
_, err = NewVerifierWithOptions(store, VerifierOptions{
OCITrustPolicy: &ociPolicy,
PluginManager: pm,
})
if err != nil {
t.Fatalf("expected NewVerifierWithOptions constructor to succeed, but got %v", err)
}
@ -770,19 +786,11 @@ func TestNewVerifierWithOptions(t *testing.T) {
if err != nil {
t.Fatal(err)
}
opts = VerifierOptions{
v, err = NewVerifierWithOptions(store, VerifierOptions{
RevocationCodeSigningValidator: csValidator,
}
v, err = NewVerifierWithOptions(&ociPolicy, nil, store, pm, opts)
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)
OCITrustPolicy: &ociPolicy,
PluginManager: pm,
})
if err != nil {
t.Fatalf("expected NewVerifierWithOptions constructor to succeed, but got %v", err)
}
@ -803,22 +811,68 @@ func TestNewVerifierWithOptionsError(t *testing.T) {
if err != nil {
t.Fatalf("unexpected error while creating revocation timestamp object: %v", err)
}
opts := VerifierOptions{
_, err = NewVerifierWithOptions(store, VerifierOptions{
RevocationClient: r,
RevocationTimestampingValidator: rt,
}
_, err = NewVerifierWithOptions(nil, nil, store, pm, opts)
PluginManager: pm,
})
if err == nil || err.Error() != "ociTrustPolicy and blobTrustPolicy both cannot be nil" {
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" {
t.Errorf("expected err but not found.")
}
}
func TestNewOCIVerifierFromConfig(t *testing.T) {
defer func(oldUserConfigDir string) {
dir.UserConfigDir = oldUserConfigDir
}(dir.UserConfigDir)
tempRoot := t.TempDir()
dir.UserConfigDir = tempRoot
path := filepath.Join(tempRoot, "trustpolicy.oci.json")
policyJson, _ := json.Marshal(dummyOCIPolicyDocument())
if err := os.WriteFile(path, policyJson, 0600); err != nil {
t.Fatalf("TestLoadOCIDocument write policy file failed. Error: %v", err)
}
t.Cleanup(func() { os.RemoveAll(tempRoot) })
_, err := NewOCIVerifierFromConfig()
if err != nil {
t.Fatalf("expected NewOCIVerifierFromConfig constructor to succeed, but got %v", err)
}
}
func TestNewBlobVerifierFromConfig(t *testing.T) {
defer func(oldUserConfigDir string) {
dir.UserConfigDir = oldUserConfigDir
}(dir.UserConfigDir)
tempRoot := t.TempDir()
dir.UserConfigDir = tempRoot
path := filepath.Join(tempRoot, "trustpolicy.blob.json")
policyJson, _ := json.Marshal(dummyBlobPolicyDocument())
if err := os.WriteFile(path, policyJson, 0600); err != nil {
t.Fatalf("TestLoadBlobDocument write policy file failed. Error: %v", err)
}
t.Cleanup(func() { os.RemoveAll(tempRoot) })
_, err := NewBlobVerifierFromConfig()
if err != nil {
t.Fatalf("expected NewBlobVerifierFromConfig constructor to succeed, but got %v", err)
}
}
func TestVerifyBlob(t *testing.T) {
policy := &trustpolicy.BlobDocument{
Version: "1.0",
@ -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 {
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 {
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{}
func (ts *testTrustStore) GetCertificates(_ context.Context, _ truststore.Type, _ string) ([]*x509.Certificate, error) {