From 1619d18a1e5f0b0336cfd73ba25d84ad7048c2a8 Mon Sep 17 00:00:00 2001 From: Patrick Zheng Date: Tue, 24 Dec 2024 17:05:18 +0800 Subject: [PATCH 01/53] blob signing Signed-off-by: Patrick Zheng --- cmd/notation/blob/cmd.go | 30 ++++ cmd/notation/blob/sign.go | 184 ++++++++++++++++++++ cmd/notation/blob/sign_test.go | 295 +++++++++++++++++++++++++++++++++ cmd/notation/main.go | 2 + internal/cmd/signer.go | 41 ++++- specs/commandline/blob.md | 18 +- 6 files changed, 558 insertions(+), 12 deletions(-) create mode 100644 cmd/notation/blob/cmd.go create mode 100644 cmd/notation/blob/sign.go create mode 100644 cmd/notation/blob/sign_test.go diff --git a/cmd/notation/blob/cmd.go b/cmd/notation/blob/cmd.go new file mode 100644 index 000000000..2883acabf --- /dev/null +++ b/cmd/notation/blob/cmd.go @@ -0,0 +1,30 @@ +// 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 blob + +import "github.com/spf13/cobra" + +func Cmd() *cobra.Command { + command := &cobra.Command{ + Use: "blob", + Short: "Commands for blob", + Long: "Sign, verify, inspect signatures of blob. Configure blob trust policy.", + } + + command.AddCommand( + signCommand(nil), + ) + + return command +} diff --git a/cmd/notation/blob/sign.go b/cmd/notation/blob/sign.go new file mode 100644 index 000000000..a8142b908 --- /dev/null +++ b/cmd/notation/blob/sign.go @@ -0,0 +1,184 @@ +// 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 blob + +import ( + "errors" + "fmt" + "os" + "path/filepath" + "strings" + "time" + + "github.com/notaryproject/notation-go" + "github.com/notaryproject/notation/cmd/notation/internal/cmdutil" + "github.com/notaryproject/notation/internal/cmd" + "github.com/notaryproject/notation/internal/envelope" + "github.com/notaryproject/notation/internal/osutil" + "github.com/spf13/cobra" +) + +type blobSignOpts struct { + cmd.LoggingFlagOpts + cmd.SignerFlagOpts + expiry time.Duration + pluginConfig []string + userMetadata []string + blobPath string + signatureDirectory string + tsaServerURL string + tsaRootCertificatePath string + force bool +} + +func signCommand(opts *blobSignOpts) *cobra.Command { + if opts == nil { + opts = &blobSignOpts{} + } + longMessage := `Sign blob artifacts + +Note: a signing key must be specified. This can be done temporarily by specifying a key ID, or a new key can be configured using the command "notation key add" + +Example - Sign a blob artifact using the default signing key, with the default JWS envelope, and store the signature at current directory: + notation blob sign + +Example - Sign a blob artifact by generating the signature in a particular directory: + notation blob sign --signature-directory + +Example - Sign a blob artifact and skip user confirmations when overwriting existing signature: + notation blob sign --force + +Example - Sign a blob artifact using the default signing key, with the COSE envelope: + notation blob sign --signature-format cose + +Example - Sign a blob artifact with a specified plugin and signing key stored in KMS: + notation blob sign --plugin --id + +Example - Sign a blob artifact and add a user metadata to payload: + notation blob sign --user-metadata + +Example - Sign a blob artifact using a specified media type: + notation blob sign --media-type + +Example - Sign a blob artifact using a specified key: + notation blob sign --key + +Example - Sign a blob artifact and specify the signature expiry duration, for example 24 hours: + notation blob sign --expiry 24h + +Example - Sign a blob artifact with timestamping: + notation blob sign --timestamp-url --timestamp-root-cert /@ +` + + command := &cobra.Command{ + Use: "blob sign [flags] ", + Short: "Sign blob artifacts", + Long: longMessage, + Args: func(cmd *cobra.Command, args []string) error { + if len(args) == 0 { + return errors.New("missing file path to the blob artifact: use `notation blob sign --help` to see what parameters are required") + } + opts.blobPath = args[0] + return nil + }, + RunE: func(cmd *cobra.Command, args []string) error { + return runBlobSign(cmd, opts) + }, + } + opts.LoggingFlagOpts.ApplyFlags(command.Flags()) + opts.SignerFlagOpts.ApplyFlagsToCommand(command) + cmd.SetPflagExpiry(command.Flags(), &opts.expiry) + cmd.SetPflagPluginConfig(command.Flags(), &opts.pluginConfig) + cmd.SetPflagUserMetadata(command.Flags(), &opts.userMetadata, cmd.PflagUserMetadataSignUsage) + command.Flags().StringVar(&opts.signatureDirectory, "signature-directory", ".", "directory where the blob signature needs to be placed") + command.Flags().StringVar(&opts.tsaServerURL, "timestamp-url", "", "RFC 3161 Timestamping Authority (TSA) server URL") + command.Flags().StringVar(&opts.tsaRootCertificatePath, "timestamp-root-cert", "", "filepath of timestamp authority root certificate") + command.Flags().BoolVar(&opts.force, "force", false, "override the existing signature file, never prompt") + return command +} + +func runBlobSign(command *cobra.Command, cmdOpts *blobSignOpts) error { + // set log level + ctx := cmdOpts.LoggingFlagOpts.InitializeLogger(command.Context()) + + signer, err := cmd.GetBlobSigner(ctx, &cmdOpts.SignerFlagOpts) + if err != nil { + return err + } + blobOpts, err := prepareBlobSigningOpts(cmdOpts) + if err != nil { + return err + } + contents, err := os.ReadFile(cmdOpts.blobPath) + if err != nil { + return err + } + // core process + sig, _, err := notation.SignBlob(ctx, signer, strings.NewReader(string(contents)), blobOpts) + if err != nil { + return err + } + signaturePath := signatureFilepath(cmdOpts.signatureDirectory, cmdOpts.blobPath, cmdOpts.SignatureFormat) + // optional confirmation + if !cmdOpts.force { + if _, err := os.Stat(signaturePath); err == nil { + confirmed, err := cmdutil.AskForConfirmation(os.Stdin, "The signature file already exists, do you want to overwrite it?", cmdOpts.force) + if err != nil { + return err + } + if !confirmed { + return nil + } + } + } else { + fmt.Fprintln(os.Stderr, "Warning: existing signature file will be overwritten") + } + // write signature to file + if err := osutil.WriteFile(signaturePath, sig); err != nil { + return fmt.Errorf("failed to write signature file: %w", err) + } + + fmt.Printf("Successfully signed %s. Saved signature file at %s\n", cmdOpts.blobPath, signaturePath) + return nil +} + +func prepareBlobSigningOpts(opts *blobSignOpts) (notation.SignBlobOptions, error) { + mediaType, err := envelope.GetEnvelopeMediaType(opts.SignerFlagOpts.SignatureFormat) + if err != nil { + return notation.SignBlobOptions{}, err + } + pluginConfig, err := cmd.ParseFlagMap(opts.pluginConfig, cmd.PflagPluginConfig.Name) + if err != nil { + return notation.SignBlobOptions{}, err + } + userMetadata, err := cmd.ParseFlagMap(opts.userMetadata, cmd.PflagUserMetadata.Name) + if err != nil { + return notation.SignBlobOptions{}, err + } + blobOpts := notation.SignBlobOptions{ + SignerSignOptions: notation.SignerSignOptions{ + SignatureMediaType: mediaType, + ExpiryDuration: opts.expiry, + PluginConfig: pluginConfig, + }, + UserMetadata: userMetadata, + } + return blobOpts, nil +} + +func signatureFilepath(signatureDirectory, blobPath, signatureFormat string) string { + blobFilename := filepath.Base(blobPath) + signatureFilename := fmt.Sprintf("%s.%s.sig", blobFilename, signatureFormat) + return filepath.Join(signatureDirectory, signatureFilename) +} diff --git a/cmd/notation/blob/sign_test.go b/cmd/notation/blob/sign_test.go new file mode 100644 index 000000000..c88079f1e --- /dev/null +++ b/cmd/notation/blob/sign_test.go @@ -0,0 +1,295 @@ +// 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 blob + +import ( + "fmt" + "reflect" + "testing" + "time" + + "github.com/notaryproject/notation/internal/cmd" + "github.com/notaryproject/notation/internal/envelope" +) + +func TestBlobSignCommand_BasicArgs(t *testing.T) { + opts := &blobSignOpts{} + command := signCommand(opts) + expected := &blobSignOpts{ + blobPath: "path", + SignerFlagOpts: cmd.SignerFlagOpts{ + Key: "key", + SignatureFormat: envelope.JWS, + }, + } + if err := command.ParseFlags([]string{ + expected.blobPath, + "--key", expected.Key}); err != nil { + t.Fatalf("Parse Flag failed: %v", err) + } + if err := command.Args(command, command.Flags().Args()); err != nil { + t.Fatalf("Parse args failed: %v", err) + } + if !reflect.DeepEqual(*expected, *opts) { + t.Fatalf("Expect blob sign opts: %v, got: %v", expected, opts) + } +} + +func TestBlobSignCommand_MoreArgs(t *testing.T) { + opts := &blobSignOpts{} + command := signCommand(opts) + expected := &blobSignOpts{ + blobPath: "path", + SignerFlagOpts: cmd.SignerFlagOpts{ + Key: "key", + SignatureFormat: envelope.COSE, + }, + expiry: 24 * time.Hour, + } + if err := command.ParseFlags([]string{ + expected.blobPath, + "--key", expected.Key, + "--signature-format", expected.SignerFlagOpts.SignatureFormat, + "--expiry", expected.expiry.String()}); err != nil { + t.Fatalf("Parse Flag failed: %v", err) + } + if err := command.Args(command, command.Flags().Args()); err != nil { + t.Fatalf("Parse args failed: %v", err) + } + if !reflect.DeepEqual(*expected, *opts) { + t.Fatalf("Expect blob sign opts: %v, got: %v", expected, opts) + } +} + +func TestBlobSignCommand_CorrectConfig(t *testing.T) { + opts := &blobSignOpts{} + command := signCommand(opts) + expected := &blobSignOpts{ + blobPath: "path", + SignerFlagOpts: cmd.SignerFlagOpts{ + Key: "key", + SignatureFormat: envelope.COSE, + }, + expiry: 365 * 24 * time.Hour, + pluginConfig: []string{"key0=val0", "key1=val1"}, + } + if err := command.ParseFlags([]string{ + expected.blobPath, + "--key", expected.Key, + "--signature-format", expected.SignerFlagOpts.SignatureFormat, + "--expiry", expected.expiry.String(), + "--plugin-config", "key0=val0", + "--plugin-config", "key1=val1"}); err != nil { + t.Fatalf("Parse Flag failed: %v", err) + } + if err := command.Args(command, command.Flags().Args()); err != nil { + t.Fatalf("Parse args failed: %v", err) + } + if !reflect.DeepEqual(*expected, *opts) { + t.Fatalf("Expect sign blob opts: %v, got: %v", expected, opts) + } + config, err := cmd.ParseFlagMap(opts.pluginConfig, cmd.PflagPluginConfig.Name) + if err != nil { + t.Fatalf("Parse plugin Config flag failed: %v", err) + } + if len(config) != 2 { + t.Fatalf("Expect plugin config number: %v, got: %v ", 2, len(config)) + } + for i := 0; i < 2; i++ { + key, val := fmt.Sprintf("key%v", i), fmt.Sprintf("val%v", i) + configVal, ok := config[key] + if !ok { + t.Fatalf("Key: %v not in config", key) + } + if val != configVal { + t.Fatalf("Value for key: %v error, got: %v, expect: %v", key, configVal, val) + } + } +} + +func TestBlobSignCommand_OnDemandKeyOptions(t *testing.T) { + opts := &blobSignOpts{} + command := signCommand(opts) + expected := &blobSignOpts{ + blobPath: "path", + SignerFlagOpts: cmd.SignerFlagOpts{ + KeyID: "keyID", + PluginName: "pluginName", + SignatureFormat: envelope.JWS, + }, + } + if err := command.ParseFlags([]string{ + expected.blobPath, + "--id", expected.KeyID, + "--plugin", expected.PluginName}); err != nil { + t.Fatalf("Parse Flag failed: %v", err) + } + if err := command.Args(command, command.Flags().Args()); err != nil { + t.Fatalf("Parse args failed: %v", err) + } + if !reflect.DeepEqual(*expected, *opts) { + t.Fatalf("Expect blob sign opts: %v, got: %v", expected, opts) + } +} + +func TestBlobSignCommand_OnDemandKeyBadOptions(t *testing.T) { + t.Run("error when using id and plugin options with key", func(t *testing.T) { + opts := &blobSignOpts{} + command := signCommand(opts) + expected := &blobSignOpts{ + blobPath: "path", + SignerFlagOpts: cmd.SignerFlagOpts{ + KeyID: "keyID", + PluginName: "pluginName", + Key: "keyName", + SignatureFormat: envelope.JWS, + }, + } + if err := command.ParseFlags([]string{ + expected.blobPath, + "--id", expected.KeyID, + "--plugin", expected.PluginName, + "--key", expected.Key}); err != nil { + t.Fatalf("Parse Flag failed: %v", err) + } + if err := command.Args(command, command.Flags().Args()); err != nil { + t.Fatalf("Parse args failed: %v", err) + } + if !reflect.DeepEqual(*expected, *opts) { + t.Fatalf("Expect blob sign opts: %v, got: %v", expected, opts) + } + err := command.ValidateFlagGroups() + if err == nil || err.Error() != "if any flags in the group [key id] are set none of the others can be; [id key] were all set" { + t.Fatalf("Didn't get the expected error, but got: %v", err) + } + }) + t.Run("error when using key and id options", func(t *testing.T) { + opts := &blobSignOpts{} + command := signCommand(opts) + expected := &blobSignOpts{ + blobPath: "path", + SignerFlagOpts: cmd.SignerFlagOpts{ + KeyID: "keyID", + Key: "keyName", + SignatureFormat: envelope.JWS, + }, + } + if err := command.ParseFlags([]string{ + expected.blobPath, + "--id", expected.KeyID, + "--key", expected.Key}); err != nil { + t.Fatalf("Parse Flag failed: %v", err) + } + if err := command.Args(command, command.Flags().Args()); err != nil { + t.Fatalf("Parse args failed: %v", err) + } + if !reflect.DeepEqual(*expected, *opts) { + t.Fatalf("Expect blob sign opts: %v, got: %v", expected, opts) + } + err := command.ValidateFlagGroups() + if err == nil || err.Error() != "if any flags in the group [id plugin] are set they must all be set; missing [plugin]" { + t.Fatalf("Didn't get the expected error, but got: %v", err) + } + }) + t.Run("error when using key and plugin options", func(t *testing.T) { + opts := &blobSignOpts{} + command := signCommand(opts) + expected := &blobSignOpts{ + blobPath: "path", + SignerFlagOpts: cmd.SignerFlagOpts{ + PluginName: "pluginName", + Key: "keyName", + SignatureFormat: envelope.JWS, + }, + } + if err := command.ParseFlags([]string{ + expected.blobPath, + "--plugin", expected.PluginName, + "--key", expected.Key}); err != nil { + t.Fatalf("Parse Flag failed: %v", err) + } + if err := command.Args(command, command.Flags().Args()); err != nil { + t.Fatalf("Parse args failed: %v", err) + } + if !reflect.DeepEqual(*expected, *opts) { + t.Fatalf("Expect blob sign opts: %v, got: %v", expected, opts) + } + err := command.ValidateFlagGroups() + if err == nil || err.Error() != "if any flags in the group [id plugin] are set they must all be set; missing [id]" { + t.Fatalf("Didn't get the expected error, but got: %v", err) + } + }) + t.Run("error when using id option and not plugin", func(t *testing.T) { + opts := &blobSignOpts{} + command := signCommand(opts) + expected := &blobSignOpts{ + blobPath: "path", + SignerFlagOpts: cmd.SignerFlagOpts{ + KeyID: "keyID", + SignatureFormat: envelope.JWS, + }, + } + if err := command.ParseFlags([]string{ + expected.blobPath, + "--id", expected.KeyID}); err != nil { + t.Fatalf("Parse Flag failed: %v", err) + } + if err := command.Args(command, command.Flags().Args()); err != nil { + t.Fatalf("Parse args failed: %v", err) + } + if !reflect.DeepEqual(*expected, *opts) { + t.Fatalf("Expect blob sign opts: %v, got: %v", expected, opts) + } + err := command.ValidateFlagGroups() + if err == nil || err.Error() != "if any flags in the group [id plugin] are set they must all be set; missing [plugin]" { + t.Fatalf("Didn't get the expected error, but got: %v", err) + } + }) + t.Run("error when using plugin option and not id", func(t *testing.T) { + opts := &blobSignOpts{} + command := signCommand(opts) + expected := &blobSignOpts{ + blobPath: "path", + SignerFlagOpts: cmd.SignerFlagOpts{ + PluginName: "pluginName", + SignatureFormat: envelope.JWS, + }, + } + if err := command.ParseFlags([]string{ + expected.blobPath, + "--plugin", expected.PluginName}); err != nil { + t.Fatalf("Parse Flag failed: %v", err) + } + if err := command.Args(command, command.Flags().Args()); err != nil { + t.Fatalf("Parse args failed: %v", err) + } + if !reflect.DeepEqual(*expected, *opts) { + t.Fatalf("Expect blob sign opts: %v, got: %v", expected, opts) + } + err := command.ValidateFlagGroups() + if err == nil || err.Error() != "if any flags in the group [id plugin] are set they must all be set; missing [id]" { + t.Fatalf("Didn't get the expected error, but got: %v", err) + } + }) +} + +func TestBlobSignCommand_MissingArgs(t *testing.T) { + cmd := signCommand(nil) + if err := cmd.ParseFlags(nil); err != nil { + t.Fatalf("Parse Flag failed: %v", err) + } + if err := cmd.Args(cmd, cmd.Flags().Args()); err == nil { + t.Fatal("Parse Args expected error, but ok") + } +} diff --git a/cmd/notation/main.go b/cmd/notation/main.go index 712668a9f..e3c29e680 100644 --- a/cmd/notation/main.go +++ b/cmd/notation/main.go @@ -17,6 +17,7 @@ import ( "os" "github.com/notaryproject/notation-go/dir" + "github.com/notaryproject/notation/cmd/notation/blob" "github.com/notaryproject/notation/cmd/notation/cert" "github.com/notaryproject/notation/cmd/notation/plugin" "github.com/notaryproject/notation/cmd/notation/policy" @@ -62,6 +63,7 @@ func main() { logoutCommand(nil), versionCommand(), inspectCommand(nil), + blob.Cmd(), ) if err := cmd.Execute(); err != nil { os.Exit(1) diff --git a/internal/cmd/signer.go b/internal/cmd/signer.go index 39edc6fda..bb805a057 100644 --- a/internal/cmd/signer.go +++ b/internal/cmd/signer.go @@ -34,7 +34,7 @@ func GetSigner(ctx context.Context, opts *SignerFlagOpts) (notation.Signer, erro if err != nil { return nil, err } - return signer.NewFromPlugin(plugin, opts.KeyID, map[string]string{}) + return signer.NewPluginSigner(plugin, opts.KeyID, map[string]string{}) } // Construct a signer from preconfigured key pair in config.json @@ -54,7 +54,42 @@ func GetSigner(ctx context.Context, opts *SignerFlagOpts) (notation.Signer, erro if err != nil { return nil, err } - return signer.NewFromPlugin(plugin, key.ExternalKey.ID, key.PluginConfig) + return signer.NewPluginSigner(plugin, key.ExternalKey.ID, key.PluginConfig) } - return nil, errors.New("unsupported key, either provide a local key and certificate file paths, or a key name in config.json, check [DOC_PLACEHOLDER] for details") + return nil, errors.New("unsupported key, either provide a local key and certificate file paths, or a key name in config.json, check https://notaryproject.dev/docs/user-guides/how-to/notation-config-file/ for details") +} + +// GetBlobSigner returns a blob signer according to the CLI context. +func GetBlobSigner(ctx context.Context, opts *SignerFlagOpts) (notation.BlobSigner, error) { + // Check if using on-demand key + if opts.KeyID != "" && opts.PluginName != "" && opts.Key == "" { + // Construct a signer from on-demand key + mgr := plugin.NewCLIManager(dir.PluginFS()) + plugin, err := mgr.Get(ctx, opts.PluginName) + if err != nil { + return nil, err + } + return signer.NewPluginSigner(plugin, opts.KeyID, map[string]string{}) + } + + // Construct a signer from preconfigured key pair in config.json + // if key name is provided as the CLI argument + key, err := configutil.ResolveKey(opts.Key) + if err != nil { + return nil, err + } + if key.X509KeyPair != nil { + return signer.NewGenericSignerFromFiles(key.X509KeyPair.KeyPath, key.X509KeyPair.CertificatePath) + } + // Construct a plugin signer if key name provided as the CLI argument + // corresponds to an external key + if key.ExternalKey != nil { + mgr := plugin.NewCLIManager(dir.PluginFS()) + plugin, err := mgr.Get(ctx, key.PluginName) + if err != nil { + return nil, err + } + return signer.NewPluginSigner(plugin, key.ExternalKey.ID, key.PluginConfig) + } + return nil, errors.New("unsupported key, either provide a local key and certificate file paths, or a key name in config.json, check https://notaryproject.dev/docs/user-guides/how-to/notation-config-file/ for details") } diff --git a/specs/commandline/blob.md b/specs/commandline/blob.md index 1fdb76ec6..1c93e453e 100644 --- a/specs/commandline/blob.md +++ b/specs/commandline/blob.md @@ -2,7 +2,7 @@ ## Description -Use `notation blob` command to sign, verify, and inspect signatures associated with arbitrary blobs. Notation can sign and verify any arbitrary bag of bits like zip files, documents, executables, etc. When a user signs a blob, `notation` produces a detached signature, which the user can transport/distribute using any medium that the user prefers along with the original blob. On the verification side, Notation can verify the blob's signature and assert that the blob has not been tampered with during its transmission. +Use `notation blob` command to sign, verify, and inspect signatures associated with arbitrary blobs. Notation can sign and verify any arbitrary bag of bits like zip files, documents, executables, etc. When a user signs a blob, `notation` produces a detached signature, which the user can transport/distribute using any medium that the user prefers along with the original blob. On the verification side, Notation can verify the blob's signature and assert that the blob has not been tampered with during its transmission. Users can use `notation blob policy` command to manage trust policies for verifying a blob signature. The `notation blob policy` command provides a user-friendly way to manage trust policies for signed blobs. It allows users to show trust policy configuration, import/export a trust policy configuration file from/to a JSON file. For more details, see [blob trust policy specification and examples](https://github.com/notaryproject/specifications/blob/main/specs/trust-store-trust-policy.md#blob-trust-policy). @@ -21,7 +21,7 @@ The sample trust policy file (`trustpolicy.blob.json`) for verifying signed blob "signatureVerification": { "level": "strict" }, - "trustStores": [ + "trustStores": [ "ca:wabbit-networks", ], "trustedIdentities": [ @@ -31,7 +31,7 @@ The sample trust policy file (`trustpolicy.blob.json`) for verifying signed blob { "name": "skip-verification-policy", "signatureVerification": { - "level" : "skip" + "level" : "skip" } }, { @@ -52,7 +52,7 @@ The sample trust policy file (`trustpolicy.blob.json`) for verifying signed blob ### notation blob command ```text -Sign, inspect, and verify signatures and configure trust policies. +Sign, verify, inspect signatures of blob. Configure blob trust policy. Usage: notation blob [command] @@ -76,7 +76,7 @@ Usage: notation blob sign [flags] Flags: - --signature-directory string optional path where the blob signature needs to be placed (default: currently working directory) + --signature-directory string optional directory where the blob signature needs to be placed (default: currently working directory) --media-type string optional media type of the blob (default: "application/octet-stream") -e, --expiry duration optional expiry that provides a "best by use" time for the blob. The duration is specified in minutes(m) and/or hours(h). For example: 12h, 30m, 3h20m --id string key id (required if --plugin is set). This is mutually exclusive with the --key flag @@ -129,7 +129,7 @@ Import blob trust policy configuration from a JSON file Usage: notation blob policy import [flags] -Flags: +Flags: --force override the existing trust policy configuration, never prompt -h, --help help for import ``` @@ -329,7 +329,7 @@ notation blob inspect -o json /tmp/my-blob.bin.sig.jws An example of import trust policy configuration from a JSON file: -```shell +```shell notation blob policy import ./my_policy.json ``` @@ -387,7 +387,7 @@ The `notation blob verify` command can be used to verify blob signatures. In ord "signatureVerification": { "level": "strict" }, - "trustStores": [ + "trustStores": [ "ca:wabbit-networks", ], "trustedIdentities": [ @@ -497,4 +497,4 @@ An example of output messages for an unsuccessful verification: ```text Error: signature verification failed for policy `wabbit-networks-policy` -``` \ No newline at end of file +``` From 58704236b35a1ded8111f045c05ca670c7c59686 Mon Sep 17 00:00:00 2001 From: Patrick Zheng Date: Wed, 25 Dec 2024 15:00:40 +0800 Subject: [PATCH 02/53] blob signing Signed-off-by: Patrick Zheng --- cmd/notation/blob/sign.go | 85 +++++++++++++++++++++++++++++++------ internal/cmd/signer.go | 49 +++++++++------------ internal/cmd/signer_test.go | 43 +++++++++++++++++++ specs/commandline/blob.md | 31 ++++++++------ 4 files changed, 153 insertions(+), 55 deletions(-) create mode 100644 internal/cmd/signer_test.go diff --git a/cmd/notation/blob/sign.go b/cmd/notation/blob/sign.go index a8142b908..b34939765 100644 --- a/cmd/notation/blob/sign.go +++ b/cmd/notation/blob/sign.go @@ -14,21 +14,34 @@ package blob import ( + "context" + "crypto/x509" "errors" "fmt" + "net/http" "os" "path/filepath" - "strings" "time" + "github.com/notaryproject/notation-core-go/revocation/purpose" + corex509 "github.com/notaryproject/notation-core-go/x509" "github.com/notaryproject/notation-go" + "github.com/notaryproject/notation-go/log" "github.com/notaryproject/notation/cmd/notation/internal/cmdutil" "github.com/notaryproject/notation/internal/cmd" "github.com/notaryproject/notation/internal/envelope" + "github.com/notaryproject/notation/internal/httputil" "github.com/notaryproject/notation/internal/osutil" + clirev "github.com/notaryproject/notation/internal/revocation" + nx509 "github.com/notaryproject/notation/internal/x509" + "github.com/notaryproject/tspclient-go" "github.com/spf13/cobra" ) +// timestampingTimeout is the timeout when requesting timestamp countersignature +// from a TSA +const timestampingTimeout = 15 * time.Second + type blobSignOpts struct { cmd.LoggingFlagOpts cmd.SignerFlagOpts @@ -36,6 +49,7 @@ type blobSignOpts struct { pluginConfig []string userMetadata []string blobPath string + blobMediaType string signatureDirectory string tsaServerURL string tsaRootCertificatePath string @@ -46,7 +60,7 @@ func signCommand(opts *blobSignOpts) *cobra.Command { if opts == nil { opts = &blobSignOpts{} } - longMessage := `Sign blob artifacts + longMessage := `Produce a detached signature for a given blob. Note: a signing key must be specified. This can be done temporarily by specifying a key ID, or a new key can be configured using the command "notation key add" @@ -82,7 +96,7 @@ Example - Sign a blob artifact with timestamping: ` command := &cobra.Command{ - Use: "blob sign [flags] ", + Use: "sign [flags] ", Short: "Sign blob artifacts", Long: longMessage, Args: func(cmd *cobra.Command, args []string) error { @@ -101,6 +115,7 @@ Example - Sign a blob artifact with timestamping: cmd.SetPflagExpiry(command.Flags(), &opts.expiry) cmd.SetPflagPluginConfig(command.Flags(), &opts.pluginConfig) cmd.SetPflagUserMetadata(command.Flags(), &opts.userMetadata, cmd.PflagUserMetadataSignUsage) + command.Flags().StringVar(&opts.blobMediaType, "media-type", "application/octet-stream", "media type of the blob") command.Flags().StringVar(&opts.signatureDirectory, "signature-directory", ".", "directory where the blob signature needs to be placed") command.Flags().StringVar(&opts.tsaServerURL, "timestamp-url", "", "RFC 3161 Timestamping Authority (TSA) server URL") command.Flags().StringVar(&opts.tsaRootCertificatePath, "timestamp-root-cert", "", "filepath of timestamp authority root certificate") @@ -116,20 +131,24 @@ func runBlobSign(command *cobra.Command, cmdOpts *blobSignOpts) error { if err != nil { return err } - blobOpts, err := prepareBlobSigningOpts(cmdOpts) + blobOpts, err := prepareBlobSigningOpts(ctx, cmdOpts) if err != nil { return err } - contents, err := os.ReadFile(cmdOpts.blobPath) + blobFile, err := os.Open(cmdOpts.blobPath) if err != nil { return err } + defer blobFile.Close() + // core process - sig, _, err := notation.SignBlob(ctx, signer, strings.NewReader(string(contents)), blobOpts) + sig, _, err := notation.SignBlob(ctx, signer, blobFile, blobOpts) if err != nil { return err } signaturePath := signatureFilepath(cmdOpts.signatureDirectory, cmdOpts.blobPath, cmdOpts.SignatureFormat) + fmt.Printf("Writing signature to file %q...\n", signaturePath) + // optional confirmation if !cmdOpts.force { if _, err := os.Stat(signaturePath); err == nil { @@ -144,16 +163,18 @@ func runBlobSign(command *cobra.Command, cmdOpts *blobSignOpts) error { } else { fmt.Fprintln(os.Stderr, "Warning: existing signature file will be overwritten") } + // write signature to file if err := osutil.WriteFile(signaturePath, sig); err != nil { - return fmt.Errorf("failed to write signature file: %w", err) + return fmt.Errorf("failed to write signature to file: %w", err) } - - fmt.Printf("Successfully signed %s. Saved signature file at %s\n", cmdOpts.blobPath, signaturePath) + fmt.Printf("Successfully signed %q. Saved signature file at %q\n", cmdOpts.blobPath, signaturePath) return nil } -func prepareBlobSigningOpts(opts *blobSignOpts) (notation.SignBlobOptions, error) { +func prepareBlobSigningOpts(ctx context.Context, opts *blobSignOpts) (notation.SignBlobOptions, error) { + logger := log.GetLogger(ctx) + mediaType, err := envelope.GetEnvelopeMediaType(opts.SignerFlagOpts.SignatureFormat) if err != nil { return notation.SignBlobOptions{}, err @@ -166,17 +187,55 @@ func prepareBlobSigningOpts(opts *blobSignOpts) (notation.SignBlobOptions, error if err != nil { return notation.SignBlobOptions{}, err } - blobOpts := notation.SignBlobOptions{ + signBlobOpts := notation.SignBlobOptions{ SignerSignOptions: notation.SignerSignOptions{ SignatureMediaType: mediaType, ExpiryDuration: opts.expiry, PluginConfig: pluginConfig, }, - UserMetadata: userMetadata, + ContentMediaType: opts.blobMediaType, + UserMetadata: userMetadata, + } + if opts.tsaServerURL != "" { + // timestamping + logger.Infof("Configured to timestamp with TSA %q", opts.tsaServerURL) + signBlobOpts.Timestamper, err = tspclient.NewHTTPTimestamper(httputil.NewClient(ctx, &http.Client{Timeout: timestampingTimeout}), opts.tsaServerURL) + if err != nil { + return notation.SignBlobOptions{}, fmt.Errorf("cannot get http timestamper for timestamping: %w", err) + } + + rootCerts, err := corex509.ReadCertificateFile(opts.tsaRootCertificatePath) + if err != nil { + return notation.SignBlobOptions{}, err + } + if len(rootCerts) == 0 { + return notation.SignBlobOptions{}, fmt.Errorf("cannot find any certificate from %q. Expecting single x509 root certificate in PEM or DER format from the file", opts.tsaRootCertificatePath) + } + if len(rootCerts) > 1 { + return notation.SignBlobOptions{}, fmt.Errorf("found more than one certificates from %q. Expecting single x509 root certificate in PEM or DER format from the file", opts.tsaRootCertificatePath) + } + tsaRootCert := rootCerts[0] + isRoot, err := nx509.IsRootCertificate(tsaRootCert) + if err != nil { + return notation.SignBlobOptions{}, fmt.Errorf("failed to check root certificate with error: %w", err) + } + if !isRoot { + return notation.SignBlobOptions{}, fmt.Errorf("certificate from %q is not a root certificate. Expecting single x509 root certificate in PEM or DER format from the file", opts.tsaRootCertificatePath) + + } + rootCAs := x509.NewCertPool() + rootCAs.AddCert(tsaRootCert) + signBlobOpts.TSARootCAs = rootCAs + tsaRevocationValidator, err := clirev.NewRevocationValidator(ctx, purpose.Timestamping) + if err != nil { + return notation.SignBlobOptions{}, fmt.Errorf("failed to create timestamping revocation validator: %w", err) + } + signBlobOpts.TSARevocationValidator = tsaRevocationValidator } - return blobOpts, nil + return signBlobOpts, nil } +// signatureFilepath returns the path to the signature file. func signatureFilepath(signatureDirectory, blobPath, signatureFormat string) string { blobFilename := filepath.Base(blobPath) signatureFilename := fmt.Sprintf("%s.%s.sig", blobFilename, signatureFormat) diff --git a/internal/cmd/signer.go b/internal/cmd/signer.go index bb805a057..66ffc9e90 100644 --- a/internal/cmd/signer.go +++ b/internal/cmd/signer.go @@ -26,41 +26,33 @@ import ( // GetSigner returns a signer according to the CLI context. func GetSigner(ctx context.Context, opts *SignerFlagOpts) (notation.Signer, error) { - // Check if using on-demand key - if opts.KeyID != "" && opts.PluginName != "" && opts.Key == "" { - // Construct a signer from on-demand key - mgr := plugin.NewCLIManager(dir.PluginFS()) - plugin, err := mgr.Get(ctx, opts.PluginName) - if err != nil { - return nil, err - } - return signer.NewPluginSigner(plugin, opts.KeyID, map[string]string{}) - } - - // Construct a signer from preconfigured key pair in config.json - // if key name is provided as the CLI argument - key, err := configutil.ResolveKey(opts.Key) + s, err := signerCore(ctx, opts) if err != nil { return nil, err } - if key.X509KeyPair != nil { - return signer.NewFromFiles(key.X509KeyPair.KeyPath, key.X509KeyPair.CertificatePath) - } - // Construct a plugin signer if key name provided as the CLI argument - // corresponds to an external key - if key.ExternalKey != nil { - mgr := plugin.NewCLIManager(dir.PluginFS()) - plugin, err := mgr.Get(ctx, key.PluginName) - if err != nil { - return nil, err - } - return signer.NewPluginSigner(plugin, key.ExternalKey.ID, key.PluginConfig) - } - return nil, errors.New("unsupported key, either provide a local key and certificate file paths, or a key name in config.json, check https://notaryproject.dev/docs/user-guides/how-to/notation-config-file/ for details") + + // always true, as signerCore returns either signer.PluginSigner or + // signer.GenericSigner. + notationSigner, _ := s.(notation.Signer) + return notationSigner, nil } // GetBlobSigner returns a blob signer according to the CLI context. func GetBlobSigner(ctx context.Context, opts *SignerFlagOpts) (notation.BlobSigner, error) { + s, err := signerCore(ctx, opts) + if err != nil { + return nil, err + } + + // always true, as signerCore returns either signer.PluginSigner or + // signer.GenericSigner. + notationBlobSigner, _ := s.(notation.BlobSigner) + return notationBlobSigner, nil +} + +// signerCore returns a signer.PluginSigner or signer.GenericSigner based on +// user opts. +func signerCore(ctx context.Context, opts *SignerFlagOpts) (any, error) { // Check if using on-demand key if opts.KeyID != "" && opts.PluginName != "" && opts.Key == "" { // Construct a signer from on-demand key @@ -81,6 +73,7 @@ func GetBlobSigner(ctx context.Context, opts *SignerFlagOpts) (notation.BlobSign if key.X509KeyPair != nil { return signer.NewGenericSignerFromFiles(key.X509KeyPair.KeyPath, key.X509KeyPair.CertificatePath) } + // Construct a plugin signer if key name provided as the CLI argument // corresponds to an external key if key.ExternalKey != nil { diff --git a/internal/cmd/signer_test.go b/internal/cmd/signer_test.go new file mode 100644 index 000000000..060c95d46 --- /dev/null +++ b/internal/cmd/signer_test.go @@ -0,0 +1,43 @@ +// Copyright The Notary Project Authors. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package cmd + +import ( + "testing" + + "github.com/notaryproject/notation-go" + "github.com/notaryproject/notation-go/signer" +) + +func TestGenericSignerImpl(t *testing.T) { + g := &signer.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 TestPluginSignerImpl(t *testing.T) { + p := &signer.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") + } +} diff --git a/specs/commandline/blob.md b/specs/commandline/blob.md index 1c93e453e..1d1a90c32 100644 --- a/specs/commandline/blob.md +++ b/specs/commandline/blob.md @@ -70,24 +70,27 @@ Flags: ### notation blob sign ```text -Produce a signature for a given blob. A detached signature file will be written to the currently working directory with blob file name + ".sig" + signature format as the file extension. For example, signature file name for "myBlob.bin" will be "myBlob.bin.sig.jws" for JWS signature format or "myBlob.bin.sig.cose" for COSE signature format. +Produce a detached signature for a given blob. The signature file will be written to the currently working directory with `blob file name` + `signature format` + `.sig` as the signature's file name. For example, signature file name for "myBlob.bin" will be "myBlob.bin.sig.jws" for JWS signature format or "myBlob.bin.sig.cose" for COSE signature format. Usage: - notation blob sign [flags] + notation blob sign [flags] Flags: - --signature-directory string optional directory where the blob signature needs to be placed (default: currently working directory) - --media-type string optional media type of the blob (default: "application/octet-stream") - -e, --expiry duration optional expiry that provides a "best by use" time for the blob. The duration is specified in minutes(m) and/or hours(h). For example: 12h, 30m, 3h20m - --id string key id (required if --plugin is set). This is mutually exclusive with the --key flag - -k, --key string signing key name, for a key previously added to notation's key list. This is mutually exclusive with the --id and --plugin flags - --plugin string signing plugin name. This is mutually exclusive with the --key flag - --plugin-config stringArray {key}={value} pairs that are passed as it is to a plugin, refer plugin's documentation to set appropriate values. - --signature-format string signature envelope format, options: "jws", "cose" (default "jws") - -m, --user-metadata stringArray {key}={value} pairs that are added to the signature payload - -d, --debug debug mode - -v, --verbose verbose mode - -h, --help help for sign + -d, --debug debug mode + -e, --expiry duration optional expiry that provides a "best by use" time for the artifact. The duration is specified in minutes(m) and/or hours(h). For example: 12h, 30m, 3h20m + --force override the existing signature file, never prompt + -h, --help help for sign + --id string key id (required if --plugin is set). This is mutually exclusive with the --key flag + -k, --key string signing key name, for a key previously added to notation's key list. This is mutually exclusive with the --id and --plugin flags + --media-type string media type of the blob (default "application/octet-stream") + --plugin string signing plugin name (required if --id is set). This is mutually exclusive with the --key flag + --plugin-config stringArray {key}={value} pairs that are passed as it is to a plugin, refer plugin's documentation to set appropriate values + --signature-directory string directory where the blob signature needs to be placed (default ".") + --signature-format string signature envelope format, options: "jws", "cose" (default "jws") + --timestamp-root-cert string filepath of timestamp authority root certificate + --timestamp-url string RFC 3161 Timestamping Authority (TSA) server URL + -m, --user-metadata stringArray {key}={value} pairs that are added to the signature payload + -v, --verbose verbose mode ``` ### notation blob inspect From c6da7dcc7103e57192ab88f98ec3fb07ad439049 Mon Sep 17 00:00:00 2001 From: Patrick Zheng Date: Thu, 26 Dec 2024 10:31:17 +0800 Subject: [PATCH 03/53] update Signed-off-by: Patrick Zheng --- cmd/notation/blob/sign.go | 5 ++++- cmd/notation/blob/sign_test.go | 24 +++++++++++++++++++++--- specs/commandline/blob.md | 25 ++++++++++++++----------- 3 files changed, 39 insertions(+), 15 deletions(-) diff --git a/cmd/notation/blob/sign.go b/cmd/notation/blob/sign.go index b34939765..cacb75c34 100644 --- a/cmd/notation/blob/sign.go +++ b/cmd/notation/blob/sign.go @@ -62,6 +62,8 @@ func signCommand(opts *blobSignOpts) *cobra.Command { } longMessage := `Produce a detached signature for a given blob. +The signature file will be written to the currently working directory with file name "{blob file name}.{signature format}.sig". + Note: a signing key must be specified. This can be done temporarily by specifying a key ID, or a new key can be configured using the command "notation key add" Example - Sign a blob artifact using the default signing key, with the default JWS envelope, and store the signature at current directory: @@ -168,7 +170,8 @@ func runBlobSign(command *cobra.Command, cmdOpts *blobSignOpts) error { if err := osutil.WriteFile(signaturePath, sig); err != nil { return fmt.Errorf("failed to write signature to file: %w", err) } - fmt.Printf("Successfully signed %q. Saved signature file at %q\n", cmdOpts.blobPath, signaturePath) + fmt.Printf("Successfully signed %q\n ", cmdOpts.blobPath) + fmt.Printf("Signature file written to %q\n", signaturePath) return nil } diff --git a/cmd/notation/blob/sign_test.go b/cmd/notation/blob/sign_test.go index c88079f1e..78f74e972 100644 --- a/cmd/notation/blob/sign_test.go +++ b/cmd/notation/blob/sign_test.go @@ -32,6 +32,8 @@ func TestBlobSignCommand_BasicArgs(t *testing.T) { Key: "key", SignatureFormat: envelope.JWS, }, + signatureDirectory: ".", + blobMediaType: "application/octet-stream", } if err := command.ParseFlags([]string{ expected.blobPath, @@ -55,7 +57,9 @@ func TestBlobSignCommand_MoreArgs(t *testing.T) { Key: "key", SignatureFormat: envelope.COSE, }, - expiry: 24 * time.Hour, + expiry: 24 * time.Hour, + signatureDirectory: ".", + blobMediaType: "application/octet-stream", } if err := command.ParseFlags([]string{ expected.blobPath, @@ -81,8 +85,10 @@ func TestBlobSignCommand_CorrectConfig(t *testing.T) { Key: "key", SignatureFormat: envelope.COSE, }, - expiry: 365 * 24 * time.Hour, - pluginConfig: []string{"key0=val0", "key1=val1"}, + expiry: 365 * 24 * time.Hour, + pluginConfig: []string{"key0=val0", "key1=val1"}, + signatureDirectory: ".", + blobMediaType: "application/octet-stream", } if err := command.ParseFlags([]string{ expected.blobPath, @@ -128,6 +134,8 @@ func TestBlobSignCommand_OnDemandKeyOptions(t *testing.T) { PluginName: "pluginName", SignatureFormat: envelope.JWS, }, + signatureDirectory: ".", + blobMediaType: "application/octet-stream", } if err := command.ParseFlags([]string{ expected.blobPath, @@ -155,6 +163,8 @@ func TestBlobSignCommand_OnDemandKeyBadOptions(t *testing.T) { Key: "keyName", SignatureFormat: envelope.JWS, }, + signatureDirectory: ".", + blobMediaType: "application/octet-stream", } if err := command.ParseFlags([]string{ expected.blobPath, @@ -184,6 +194,8 @@ func TestBlobSignCommand_OnDemandKeyBadOptions(t *testing.T) { Key: "keyName", SignatureFormat: envelope.JWS, }, + signatureDirectory: ".", + blobMediaType: "application/octet-stream", } if err := command.ParseFlags([]string{ expected.blobPath, @@ -212,6 +224,8 @@ func TestBlobSignCommand_OnDemandKeyBadOptions(t *testing.T) { Key: "keyName", SignatureFormat: envelope.JWS, }, + signatureDirectory: ".", + blobMediaType: "application/octet-stream", } if err := command.ParseFlags([]string{ expected.blobPath, @@ -239,6 +253,8 @@ func TestBlobSignCommand_OnDemandKeyBadOptions(t *testing.T) { KeyID: "keyID", SignatureFormat: envelope.JWS, }, + signatureDirectory: ".", + blobMediaType: "application/octet-stream", } if err := command.ParseFlags([]string{ expected.blobPath, @@ -265,6 +281,8 @@ func TestBlobSignCommand_OnDemandKeyBadOptions(t *testing.T) { PluginName: "pluginName", SignatureFormat: envelope.JWS, }, + signatureDirectory: ".", + blobMediaType: "application/octet-stream", } if err := command.ParseFlags([]string{ expected.blobPath, diff --git a/specs/commandline/blob.md b/specs/commandline/blob.md index 1d1a90c32..acf40f81e 100644 --- a/specs/commandline/blob.md +++ b/specs/commandline/blob.md @@ -22,7 +22,7 @@ The sample trust policy file (`trustpolicy.blob.json`) for verifying signed blob "level": "strict" }, "trustStores": [ - "ca:wabbit-networks", + "ca:wabbit-networks" ], "trustedIdentities": [ "x509.subject: C=US, ST=WA, L=Seattle, O=wabbit-networks.io, OU=Security Tools" @@ -70,7 +70,9 @@ Flags: ### notation blob sign ```text -Produce a detached signature for a given blob. The signature file will be written to the currently working directory with `blob file name` + `signature format` + `.sig` as the signature's file name. For example, signature file name for "myBlob.bin" will be "myBlob.bin.sig.jws" for JWS signature format or "myBlob.bin.sig.cose" for COSE signature format. +Produce a detached signature for a given blob. + +The signature file will be written to the currently working directory with file name "{blob file name}.{signature format}.sig". Usage: notation blob sign [flags] @@ -172,6 +174,7 @@ Flags: ## Usage ## Produce blob signatures +The signature file will be written to the currently working directory with file name "{blob file name}.{signature format}.sig". For example, signature file name for "myBlob.bin" will be "myBlob.bin.jws.sig" for JWS signature format or "myBlob.bin.cose.sig" for COSE signature format. ### Sign a blob by adding a new key @@ -191,22 +194,22 @@ An example for a successful signing: ```console $ notation blob sign /tmp/my-blob.bin -Successfully signed /tmp/my-blob.bin -Signature file written to /absolute/path/to/cwd/my-blob.bin.sig.jws +Successfully signed "/tmp/my-blob.bin" +Signature file written to "/absolute/path/to/cwd/my-blob.bin.jws.sig" ``` ### Sign a blob by generating the signature in a particular directory ```console $ notation blob sign --signature-directory /tmp/xyz/sigs /tmp/my-blob.bin -Successfully signed /tmp/my-blob.bin -Signature file written to /tmp/xyz/sigs/my-blob.bin.sig.jws +Successfully signed "/tmp/my-blob.bin" +Signature file written to "/tmp/xyz/sigs/my-blob.bin.jws.sig" ``` ### Sign a blob using a relative path ```console $ notation blob sign ./relative/path/my-blob.bin -Successfully signed ./relative/path/my-blob.bin -Signature file written to /absolute/path/to/cwd/my-blob.bin.sig.jws +Successfully signed "./relative/path/my-blob.bin" +Signature file written to "/absolute/path/to/cwd/my-blob.bin.jws.sig" ``` ### Sign a blob with a plugin @@ -223,8 +226,8 @@ notation blob sign --plugin --id /tmp/my-blob.bin # Use option "--signature-format" to set the signature format to COSE. $ notation blob sign --signature-format cose /tmp/my-blob.bin -Successfully signed /tmp/my-blob.bin -Signature file written to /absolute/path/to/cwd/my-blob.bin.sig.cose +Successfully signed "/tmp/my-blob.bin" +Signature file written to "/absolute/path/to/cwd/my-blob.bin.cose.sig" ``` ### Sign a blob using the default signing key @@ -391,7 +394,7 @@ The `notation blob verify` command can be used to verify blob signatures. In ord "level": "strict" }, "trustStores": [ - "ca:wabbit-networks", + "ca:wabbit-networks" ], "trustedIdentities": [ "x509.subject: C=US, ST=WA, L=Seattle, O=wabbit-networks.io, OU=Security Tools" From 97c3fcda356d2bbed557b8c5b9da6e051bfc32c4 Mon Sep 17 00:00:00 2001 From: Patrick Zheng Date: Thu, 26 Dec 2024 16:07:29 +0800 Subject: [PATCH 04/53] add tests Signed-off-by: Patrick Zheng --- cmd/notation/blob/testdata/config.json | 3 + .../invalid_signingkeys/signingkeys.json | 17 ++ .../plugins/testPlugin/notation-testplugin | 0 .../valid_signingkeys/signingkeys.json | 9 + internal/cmd/signer_test.go | 142 ++++++++++++++++ internal/cmd/testdata/config.json | 3 + .../invalid_signingkeys/signingkeys.json | 17 ++ .../plugins/testPlugin/notation-testplugin | 0 .../valid_signingkeys/signingkeys.json | 9 + test/e2e/internal/notation/host.go | 22 +++ test/e2e/internal/notation/init.go | 8 +- test/e2e/run.sh | 3 +- test/e2e/suite/command/blob/blob_test.go | 26 +++ test/e2e/suite/command/blob/sign.go | 159 ++++++++++++++++++ test/e2e/testdata/blob/blobFile | 1 + 15 files changed, 416 insertions(+), 3 deletions(-) create mode 100644 cmd/notation/blob/testdata/config.json create mode 100644 cmd/notation/blob/testdata/invalid_signingkeys/signingkeys.json create mode 100644 cmd/notation/blob/testdata/plugins/plugins/testPlugin/notation-testplugin create mode 100644 cmd/notation/blob/testdata/valid_signingkeys/signingkeys.json create mode 100644 internal/cmd/testdata/config.json create mode 100644 internal/cmd/testdata/invalid_signingkeys/signingkeys.json create mode 100644 internal/cmd/testdata/plugins/plugins/testPlugin/notation-testplugin create mode 100644 internal/cmd/testdata/valid_signingkeys/signingkeys.json create mode 100644 test/e2e/suite/command/blob/blob_test.go create mode 100644 test/e2e/suite/command/blob/sign.go create mode 100644 test/e2e/testdata/blob/blobFile diff --git a/cmd/notation/blob/testdata/config.json b/cmd/notation/blob/testdata/config.json new file mode 100644 index 000000000..c65496af3 --- /dev/null +++ b/cmd/notation/blob/testdata/config.json @@ -0,0 +1,3 @@ +{ + "insecureRegistries": ["reg1.io"] +} \ No newline at end of file diff --git a/cmd/notation/blob/testdata/invalid_signingkeys/signingkeys.json b/cmd/notation/blob/testdata/invalid_signingkeys/signingkeys.json new file mode 100644 index 000000000..a52036302 --- /dev/null +++ b/cmd/notation/blob/testdata/invalid_signingkeys/signingkeys.json @@ -0,0 +1,17 @@ +{ + "keys": [ + { + "name": "invalid", + "keypath": "", + "certpath": "" + }, + { + "name": "invalidExternal", + "id": "invalid", + "pluginName": "invalid" + }, + { + "name": "empty" + } + ] +} diff --git a/cmd/notation/blob/testdata/plugins/plugins/testPlugin/notation-testplugin b/cmd/notation/blob/testdata/plugins/plugins/testPlugin/notation-testplugin new file mode 100644 index 000000000..e69de29bb diff --git a/cmd/notation/blob/testdata/valid_signingkeys/signingkeys.json b/cmd/notation/blob/testdata/valid_signingkeys/signingkeys.json new file mode 100644 index 000000000..66f5f6c0c --- /dev/null +++ b/cmd/notation/blob/testdata/valid_signingkeys/signingkeys.json @@ -0,0 +1,9 @@ +{ + "keys": [ + { + "name": "test", + "id": "testKey", + "pluginName": "testPlugin" + } + ] +} diff --git a/internal/cmd/signer_test.go b/internal/cmd/signer_test.go index 060c95d46..fc8aadfc1 100644 --- a/internal/cmd/signer_test.go +++ b/internal/cmd/signer_test.go @@ -14,9 +14,12 @@ package cmd import ( + "context" + "runtime" "testing" "github.com/notaryproject/notation-go" + "github.com/notaryproject/notation-go/dir" "github.com/notaryproject/notation-go/signer" ) @@ -41,3 +44,142 @@ func TestPluginSignerImpl(t *testing.T) { t.Fatal("PluginSigner does not implement notation.BlobSigner") } } + +func TestGetSigner(t *testing.T) { + if runtime.GOOS == "windows" { + t.Skip("skipping test on Windows") + } + + defer func(oldLibexeDir, oldConfigDir string) { + dir.UserLibexecDir = oldLibexeDir + dir.UserConfigDir = oldConfigDir + }(dir.UserLibexecDir, dir.UserConfigDir) + + dir.UserLibexecDir = "./testdata/plugins" + dir.UserConfigDir = "./testdata/valid_signingkeys" + ctx := context.Background() + opts := &SignerFlagOpts{ + Key: "test", + } + + _, err := GetSigner(ctx, opts) + if err != nil { + t.Fatalf("expected nil error, but got %s", err) + } + + _, err = GetBlobSigner(ctx, opts) + if err != nil { + t.Fatalf("expected nil error, but got %s", err) + } +} + +func TestGetFailed(t *testing.T) { + ctx := context.Background() + opts := &SignerFlagOpts{} + + defer func(oldLibexeDir, oldConfigDir string) { + dir.UserLibexecDir = oldLibexeDir + dir.UserConfigDir = oldConfigDir + }(dir.UserLibexecDir, dir.UserConfigDir) + + dir.UserLibexecDir = "./testdata/plugins" + dir.UserConfigDir = "./testdata/invalid_signingkeys" + _, err := GetSigner(ctx, opts) + if err == nil { + t.Fatal("GetSigner should return an error") + } + + _, err = GetBlobSigner(ctx, opts) + if err == nil { + t.Fatal("GetBlobSigner should return an error") + } +} + +func TestSignerCore(t *testing.T) { + ctx := context.Background() + + defer func(oldLibexeDir, oldConfigDir string) { + dir.UserLibexecDir = oldLibexeDir + dir.UserConfigDir = oldConfigDir + }(dir.UserLibexecDir, dir.UserConfigDir) + + t.Run("invalid plugin name in opts", func(t *testing.T) { + if runtime.GOOS == "windows" { + t.Skip("skipping test on Windows") + } + + dir.UserLibexecDir = "./testdata/plugins" + dir.UserConfigDir = "./testdata/invalid_signingkeys" + opts := &SignerFlagOpts{ + KeyID: "test", + PluginName: "invalid", + } + expectedErrMsg := `plugin executable file is either not found or inaccessible: stat testdata/plugins/plugins/invalid/notation-invalid: no such file or directory` + _, err := signerCore(ctx, opts) + if err == nil || err.Error() != expectedErrMsg { + t.Fatalf("expected %s, but got %s", expectedErrMsg, err) + } + }) + + t.Run("failed to resolve key", func(t *testing.T) { + dir.UserConfigDir = "./testdata/valid_signingkeys" + expectedErrMsg := `default signing key not set. Please set default signing key or specify a key name` + _, err := signerCore(ctx, &SignerFlagOpts{}) + if err == nil || err.Error() != expectedErrMsg { + t.Fatalf("expected %s, but got %s", expectedErrMsg, err) + } + }) + + t.Run("keypath not specified", func(t *testing.T) { + dir.UserConfigDir = "./testdata/invalid_signingkeys" + expectedErrMsg := `key path not specified` + opts := &SignerFlagOpts{ + Key: "invalid", + } + _, err := signerCore(ctx, opts) + if err == nil || err.Error() != expectedErrMsg { + t.Fatalf("expected %s, but got %s", expectedErrMsg, err) + } + }) + + t.Run("key not found", func(t *testing.T) { + dir.UserConfigDir = "./testdata/valid_signingkeys" + expectedErrMsg := `signing key not found` + opts := &SignerFlagOpts{ + Key: "test2", + } + _, err := signerCore(ctx, opts) + if err == nil || err.Error() != expectedErrMsg { + t.Fatalf("expected %s, but got %s", expectedErrMsg, err) + } + }) + + t.Run("invalid plugin name in signingkeys", func(t *testing.T) { + if runtime.GOOS == "windows" { + t.Skip("skipping test on Windows") + } + + dir.UserLibexecDir = "./testdata/plugins" + dir.UserConfigDir = "./testdata/invalid_signingkeys" + expectedErrMsg := `plugin executable file is either not found or inaccessible: stat testdata/plugins/plugins/invalid/notation-invalid: no such file or directory` + opts := &SignerFlagOpts{ + Key: "invalidExternal", + } + _, err := signerCore(ctx, opts) + if err == nil || err.Error() != expectedErrMsg { + t.Fatalf("expected %s, but got %s", expectedErrMsg, err) + } + }) + + t.Run("empty key", func(t *testing.T) { + dir.UserConfigDir = "./testdata/invalid_signingkeys" + expectedErrMsg := `unsupported key, either provide a local key and certificate file paths, or a key name in config.json, check https://notaryproject.dev/docs/user-guides/how-to/notation-config-file/ for details` + opts := &SignerFlagOpts{ + Key: "empty", + } + _, err := signerCore(ctx, opts) + if err == nil || err.Error() != expectedErrMsg { + t.Fatalf("expected %s, but got %s", expectedErrMsg, err) + } + }) +} diff --git a/internal/cmd/testdata/config.json b/internal/cmd/testdata/config.json new file mode 100644 index 000000000..c65496af3 --- /dev/null +++ b/internal/cmd/testdata/config.json @@ -0,0 +1,3 @@ +{ + "insecureRegistries": ["reg1.io"] +} \ No newline at end of file diff --git a/internal/cmd/testdata/invalid_signingkeys/signingkeys.json b/internal/cmd/testdata/invalid_signingkeys/signingkeys.json new file mode 100644 index 000000000..a52036302 --- /dev/null +++ b/internal/cmd/testdata/invalid_signingkeys/signingkeys.json @@ -0,0 +1,17 @@ +{ + "keys": [ + { + "name": "invalid", + "keypath": "", + "certpath": "" + }, + { + "name": "invalidExternal", + "id": "invalid", + "pluginName": "invalid" + }, + { + "name": "empty" + } + ] +} diff --git a/internal/cmd/testdata/plugins/plugins/testPlugin/notation-testplugin b/internal/cmd/testdata/plugins/plugins/testPlugin/notation-testplugin new file mode 100644 index 000000000..e69de29bb diff --git a/internal/cmd/testdata/valid_signingkeys/signingkeys.json b/internal/cmd/testdata/valid_signingkeys/signingkeys.json new file mode 100644 index 000000000..66f5f6c0c --- /dev/null +++ b/internal/cmd/testdata/valid_signingkeys/signingkeys.json @@ -0,0 +1,9 @@ +{ + "keys": [ + { + "name": "test", + "id": "testKey", + "pluginName": "testPlugin" + } + ] +} diff --git a/test/e2e/internal/notation/host.go b/test/e2e/internal/notation/host.go index 0ad17854f..d362e1fe2 100644 --- a/test/e2e/internal/notation/host.go +++ b/test/e2e/internal/notation/host.go @@ -35,6 +35,12 @@ type CoreTestFunc func(notation *utils.ExecOpts, artifact *Artifact, vhost *util // vhost is the VirtualHost instance. type OCILayoutTestFunc func(notation *utils.ExecOpts, ocilayout *OCILayout, vhost *utils.VirtualHost) +// BlobTestFunc is the test function running in a VirtualHost for blob commands. +// +// notation is an Executor isolated by $XDG_CONFIG_HOME. +// vhost is the VirtualHost instance. +type BlobTestFunc func(notation *utils.ExecOpts, blobPath string, vhost *utils.VirtualHost) + // Host creates a virtualized notation testing host by modify // the "XDG_CONFIG_HOME" environment variable of the Executor. // @@ -87,6 +93,22 @@ func HostWithOCILayout(options []utils.HostOption, fn OCILayoutTestFunc) { fn(vhost.Executor, ocilayout, vhost) } +// HostWithBlob creates a virtualized notation testing host by modify +// the "XDG_CONFIG_HOME" environment variable of the Executor. +// +// options is the required testing environment options +// fn is the callback function containing the testing logic. +func HostWithBlob(options []utils.HostOption, fn BlobTestFunc) { + // create a notation vhost + vhost, err := createNotationHost(NotationBinPath, options...) + if err != nil { + panic(err) + } + + // run the main logic + fn(vhost.Executor, BlobPath, vhost) +} + // OldNotation create an old version notation ExecOpts in a VirtualHost // for testing forward compatibility. func OldNotation(options ...utils.HostOption) *utils.ExecOpts { diff --git a/test/e2e/internal/notation/init.go b/test/e2e/internal/notation/init.go index 110a7dde1..c082a6780 100644 --- a/test/e2e/internal/notation/init.go +++ b/test/e2e/internal/notation/init.go @@ -46,11 +46,13 @@ const ( envKeyOCILayoutPath = "NOTATION_E2E_OCI_LAYOUT_PATH" envKeyTestRepo = "NOTATION_E2E_TEST_REPO" envKeyTestTag = "NOTATION_E2E_TEST_TAG" + envKeyBlobPath = "NOTATION_E2E_BLOB_PATH" ) var ( // NotationBinPath is the notation binary path. NotationBinPath string + // NotationOldBinPath is the path of an old version notation binary for // testing forward compatibility. NotationOldBinPath string @@ -68,21 +70,23 @@ var ( TestRepoUri string TestTag string RegistryStoragePath string + BlobPath string ) func init() { RegisterFailHandler(Fail) - setUpRegistry() + setUp() setUpNotationValues() } -func setUpRegistry() { +func setUp() { setValue(envKeyRegistryHost, &TestRegistry.Host) setValue(envKeyRegistryUsername, &TestRegistry.Username) setValue(envKeyRegistryPassword, &TestRegistry.Password) setValue(envKeyDomainRegistryHost, &TestRegistry.DomainHost) setPathValue(envKeyOCILayoutPath, &OCILayoutPath) + setPathValue(envKeyBlobPath, &BlobPath) setValue(envKeyTestRepo, &TestRepoUri) setValue(envKeyTestTag, &TestTag) } diff --git a/test/e2e/run.sh b/test/e2e/run.sh index 4a8e28f05..df89d4dca 100755 --- a/test/e2e/run.sh +++ b/test/e2e/run.sh @@ -116,6 +116,7 @@ export NOTATION_E2E_TEST_TAG=v1 export NOTATION_E2E_PLUGIN_PATH=$CWD/plugin/bin/$PLUGIN_NAME export NOTATION_E2E_PLUGIN_TAR_GZ_PATH=$CWD/plugin/bin/$PLUGIN_NAME.tar.gz export NOTATION_E2E_MALICIOUS_PLUGIN_ARCHIVE_PATH=$CWD/testdata/malicious-plugin +export NOTATION_E2E_BLOB_PATH=$CWD/testdata/blob/blobFile # run tests -ginkgo -r -p -v \ No newline at end of file +ginkgo -r -p -v diff --git a/test/e2e/suite/command/blob/blob_test.go b/test/e2e/suite/command/blob/blob_test.go new file mode 100644 index 000000000..b500e346f --- /dev/null +++ b/test/e2e/suite/command/blob/blob_test.go @@ -0,0 +1,26 @@ +// 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 blob + +import ( + "testing" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +func TestCommand(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "Blob Command Suite") +} diff --git a/test/e2e/suite/command/blob/sign.go b/test/e2e/suite/command/blob/sign.go new file mode 100644 index 000000000..5365b7137 --- /dev/null +++ b/test/e2e/suite/command/blob/sign.go @@ -0,0 +1,159 @@ +// 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 blob + +import ( + . "github.com/notaryproject/notation/test/e2e/internal/notation" + "github.com/notaryproject/notation/test/e2e/internal/utils" + . "github.com/notaryproject/notation/test/e2e/suite/common" + . "github.com/onsi/ginkgo/v2" +) + +var _ = Describe("notation blob sign", func() { + It("blob sign", func() { + HostWithBlob(BaseOptions(), func(notation *utils.ExecOpts, blobPath string, vhost *utils.VirtualHost) { + notation.Exec("blob sign", blobPath). + MatchKeyWords(SignSuccessfully) + }) + }) + + It("with COSE format", func() { + HostWithBlob(BaseOptions(), func(notation *utils.ExecOpts, blobPath string, vhost *utils.VirtualHost) { + notation.Exec("blob sign", "--signature-format", "cose", blobPath). + MatchKeyWords(SignSuccessfully) + }) + }) + + // It("with specific key", func() { + // Host(BaseOptions(), func(notation *utils.ExecOpts, artifact *Artifact, vhost *utils.VirtualHost) { + // const keyName = "sKey" + // notation.Exec("cert", "generate-test", keyName). + // MatchKeyWords(fmt.Sprintf("notation/localkeys/%s.crt", keyName)) + + // notation.Exec("sign", "--key", keyName, artifact.ReferenceWithDigest()). + // MatchKeyWords(SignSuccessfully) + + // // copy the generated cert file and create the new trust policy for verify signature with generated new key. + // OldNotation(AuthOption("", ""), + // AddTrustStoreOption(keyName, vhost.AbsolutePath(NotationDirName, LocalKeysDirName, keyName+".crt")), + // AddTrustPolicyOption("generate_test_trustpolicy.json"), + // ).Exec("verify", artifact.ReferenceWithTag()). + // MatchKeyWords(VerifySuccessfully) + // }) + // }) + + // It("with expiry in 24h", func() { + // Host(BaseOptions(), func(notation *utils.ExecOpts, artifact *Artifact, vhost *utils.VirtualHost) { + // notation.Exec("sign", "--expiry", "24h", artifact.ReferenceWithDigest()). + // MatchKeyWords(SignSuccessfully) + + // OldNotation().Exec("verify", artifact.ReferenceWithTag()). + // MatchKeyWords(VerifySuccessfully) + // }) + // }) + + // It("with expiry in 2s", func() { + // Host(BaseOptions(), func(notation *utils.ExecOpts, artifact *Artifact, vhost *utils.VirtualHost) { + // notation.Exec("sign", "--expiry", "2s", artifact.ReferenceWithDigest()). + // MatchKeyWords(SignSuccessfully) + + // // sleep to wait for expiry + // time.Sleep(2100 * time.Millisecond) + + // OldNotation().ExpectFailure().Exec("verify", artifact.ReferenceWithDigest(), "-v"). + // MatchErrKeyWords("expiry validation failed."). + // MatchErrKeyWords("signature verification failed for all the signatures") + // }) + // }) + + // It("with timestamping", func() { + // Host(BaseOptions(), func(notation *utils.ExecOpts, artifact *Artifact, vhost *utils.VirtualHost) { + // notation.Exec("sign", "--timestamp-url", "http://rfc3161timestamp.globalsign.com/advanced", "--timestamp-root-cert", filepath.Join(NotationE2EConfigPath, "timestamp", "globalsignTSARoot.cer"), artifact.ReferenceWithDigest()). + // MatchKeyWords(SignSuccessfully) + // }) + // }) + + // It("with timestamp-root-cert but no timestamp-url", func() { + // Host(BaseOptions(), func(notation *utils.ExecOpts, artifact *Artifact, vhost *utils.VirtualHost) { + // notation.ExpectFailure().Exec("sign", "--timestamp-root-cert", filepath.Join(NotationE2EConfigPath, "timestamp", "globalsignTSARoot.cer"), artifact.ReferenceWithDigest()). + // MatchErrKeyWords("Error: if any flags in the group [timestamp-url timestamp-root-cert] are set they must all be set; missing [timestamp-url]") + // }) + // }) + + // It("with timestamp-url but no timestamp-root-cert", func() { + // Host(BaseOptions(), func(notation *utils.ExecOpts, artifact *Artifact, vhost *utils.VirtualHost) { + // notation.ExpectFailure().Exec("sign", "--timestamp-url", "http://rfc3161timestamp.globalsign.com/advanced", artifact.ReferenceWithDigest()). + // MatchErrKeyWords("Error: if any flags in the group [timestamp-url timestamp-root-cert] are set they must all be set; missing [timestamp-root-cert]") + // }) + // }) + + // It("with timestamping and empty tsa server", func() { + // Host(BaseOptions(), func(notation *utils.ExecOpts, artifact *Artifact, vhost *utils.VirtualHost) { + // notation.ExpectFailure().Exec("sign", "--timestamp-url", "", "--timestamp-root-cert", filepath.Join(NotationE2EConfigPath, "timestamp", "globalsignTSARoot.cer"), artifact.ReferenceWithDigest()). + // MatchErrKeyWords("Error: timestamping: tsa url cannot be empty") + // }) + // }) + + // It("with timestamping and empty tsa root cert", func() { + // Host(BaseOptions(), func(notation *utils.ExecOpts, artifact *Artifact, vhost *utils.VirtualHost) { + // notation.ExpectFailure().Exec("sign", "--timestamp-url", "dummy", "--timestamp-root-cert", "", artifact.ReferenceWithDigest()). + // MatchErrKeyWords("Error: timestamping: tsa root certificate path cannot be empty") + // }) + // }) + + // It("with timestamping and invalid tsa server", func() { + // Host(BaseOptions(), func(notation *utils.ExecOpts, artifact *Artifact, vhost *utils.VirtualHost) { + // notation.ExpectFailure().Exec("sign", "--timestamp-url", "http://invalid.com", "--timestamp-root-cert", filepath.Join(NotationE2EConfigPath, "timestamp", "globalsignTSARoot.cer"), artifact.ReferenceWithDigest()). + // MatchErrKeyWords("Error: timestamp: Post \"http://invalid.com\""). + // MatchErrKeyWords("server misbehaving") + // }) + // }) + + // It("with timestamping and invalid tsa root certificate", func() { + // Host(BaseOptions(), func(notation *utils.ExecOpts, artifact *Artifact, vhost *utils.VirtualHost) { + // notation.ExpectFailure().Exec("sign", "--timestamp-url", "http://timestamp.digicert.com", "--timestamp-root-cert", filepath.Join(NotationE2EConfigPath, "timestamp", "invalid.crt"), artifact.ReferenceWithDigest()). + // MatchErrKeyWords("Error: x509: malformed certificate") + // }) + // }) + + // It("with timestamping and empty tsa root certificate file", func() { + // Host(BaseOptions(), func(notation *utils.ExecOpts, artifact *Artifact, vhost *utils.VirtualHost) { + // notation.ExpectFailure().Exec("sign", "--timestamp-url", "http://timestamp.digicert.com", "--timestamp-root-cert", filepath.Join(NotationE2EConfigPath, "timestamp", "Empty.txt"), artifact.ReferenceWithDigest()). + // MatchErrKeyWords("cannot find any certificate from"). + // MatchErrKeyWords("Expecting single x509 root certificate in PEM or DER format from the file") + // }) + // }) + + // It("with timestamping and more than one certificates in tsa root certificate file", func() { + // Host(BaseOptions(), func(notation *utils.ExecOpts, artifact *Artifact, vhost *utils.VirtualHost) { + // notation.ExpectFailure().Exec("sign", "--timestamp-url", "http://timestamp.digicert.com", "--timestamp-root-cert", filepath.Join(NotationE2EConfigPath, "timestamp", "CertChain.pem"), artifact.ReferenceWithDigest()). + // MatchErrKeyWords("found more than one certificates"). + // MatchErrKeyWords("Expecting single x509 root certificate in PEM or DER format from the file") + // }) + // }) + + // It("with timestamping and intermediate certificate file", func() { + // Host(BaseOptions(), func(notation *utils.ExecOpts, artifact *Artifact, vhost *utils.VirtualHost) { + // notation.ExpectFailure().Exec("sign", "--timestamp-url", "http://timestamp.digicert.com", "--timestamp-root-cert", filepath.Join(NotationE2EConfigPath, "timestamp", "intermediate.pem"), artifact.ReferenceWithDigest()). + // MatchErrKeyWords("failed to check root certificate with error: crypto/rsa: verification error") + // }) + // }) + + // It("with timestamping and not self-issued certificate file", func() { + // Host(BaseOptions(), func(notation *utils.ExecOpts, artifact *Artifact, vhost *utils.VirtualHost) { + // notation.ExpectFailure().Exec("sign", "--timestamp-url", "http://timestamp.digicert.com", "--timestamp-root-cert", filepath.Join(NotationE2EConfigPath, "timestamp", "notSelfIssued.crt"), artifact.ReferenceWithDigest()). + // MatchErrKeyWords("is not a root certificate. Expecting single x509 root certificate in PEM or DER format from the file") + // }) + // }) +}) diff --git a/test/e2e/testdata/blob/blobFile b/test/e2e/testdata/blob/blobFile new file mode 100644 index 000000000..3b6067ecc --- /dev/null +++ b/test/e2e/testdata/blob/blobFile @@ -0,0 +1 @@ +test blob commands \ No newline at end of file From f0db92fb4f4c18210decb7b04a21cd5e5d572029 Mon Sep 17 00:00:00 2001 From: Patrick Zheng Date: Thu, 26 Dec 2024 16:15:16 +0800 Subject: [PATCH 05/53] fix test Signed-off-by: Patrick Zheng --- internal/cmd/testdata/valid_signingkeys/signingkeys.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/cmd/testdata/valid_signingkeys/signingkeys.json b/internal/cmd/testdata/valid_signingkeys/signingkeys.json index 66f5f6c0c..a0ee6c8d0 100644 --- a/internal/cmd/testdata/valid_signingkeys/signingkeys.json +++ b/internal/cmd/testdata/valid_signingkeys/signingkeys.json @@ -3,7 +3,7 @@ { "name": "test", "id": "testKey", - "pluginName": "testPlugin" + "pluginName": "testplugin" } ] } From 9cf65b83859806abdd5f5cda0b03899cc14522ab Mon Sep 17 00:00:00 2001 From: Patrick Zheng Date: Thu, 26 Dec 2024 16:30:25 +0800 Subject: [PATCH 06/53] fix test Signed-off-by: Patrick Zheng --- .../testPlugin/{notation-testplugin => notation-testPlugin} | 0 internal/cmd/testdata/valid_signingkeys/signingkeys.json | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename internal/cmd/testdata/plugins/plugins/testPlugin/{notation-testplugin => notation-testPlugin} (100%) diff --git a/internal/cmd/testdata/plugins/plugins/testPlugin/notation-testplugin b/internal/cmd/testdata/plugins/plugins/testPlugin/notation-testPlugin similarity index 100% rename from internal/cmd/testdata/plugins/plugins/testPlugin/notation-testplugin rename to internal/cmd/testdata/plugins/plugins/testPlugin/notation-testPlugin diff --git a/internal/cmd/testdata/valid_signingkeys/signingkeys.json b/internal/cmd/testdata/valid_signingkeys/signingkeys.json index a0ee6c8d0..66f5f6c0c 100644 --- a/internal/cmd/testdata/valid_signingkeys/signingkeys.json +++ b/internal/cmd/testdata/valid_signingkeys/signingkeys.json @@ -3,7 +3,7 @@ { "name": "test", "id": "testKey", - "pluginName": "testplugin" + "pluginName": "testPlugin" } ] } From e8fe8c67c52bc0e35b486545936e79c8ec30505e Mon Sep 17 00:00:00 2001 From: Patrick Zheng Date: Thu, 26 Dec 2024 16:37:39 +0800 Subject: [PATCH 07/53] fix e2e test Signed-off-by: Patrick Zheng --- test/e2e/suite/command/blob/sign.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/e2e/suite/command/blob/sign.go b/test/e2e/suite/command/blob/sign.go index 5365b7137..6cb10624d 100644 --- a/test/e2e/suite/command/blob/sign.go +++ b/test/e2e/suite/command/blob/sign.go @@ -23,14 +23,14 @@ import ( var _ = Describe("notation blob sign", func() { It("blob sign", func() { HostWithBlob(BaseOptions(), func(notation *utils.ExecOpts, blobPath string, vhost *utils.VirtualHost) { - notation.Exec("blob sign", blobPath). + notation.Exec("blob", "sign", blobPath). MatchKeyWords(SignSuccessfully) }) }) It("with COSE format", func() { HostWithBlob(BaseOptions(), func(notation *utils.ExecOpts, blobPath string, vhost *utils.VirtualHost) { - notation.Exec("blob sign", "--signature-format", "cose", blobPath). + notation.Exec("blob", "sign", "--signature-format", "cose", blobPath). MatchKeyWords(SignSuccessfully) }) }) From cb997c7108727e0b68c5ac4bb7031a0927e83136 Mon Sep 17 00:00:00 2001 From: Patrick Zheng Date: Thu, 26 Dec 2024 17:32:51 +0800 Subject: [PATCH 08/53] add e2e tests Signed-off-by: Patrick Zheng --- cmd/notation/blob/sign.go | 1 + test/e2e/suite/command/blob/sign.go | 259 +++++++++++++++------------- test/e2e/suite/command/sign.go | 4 +- 3 files changed, 139 insertions(+), 125 deletions(-) diff --git a/cmd/notation/blob/sign.go b/cmd/notation/blob/sign.go index cacb75c34..22ead3697 100644 --- a/cmd/notation/blob/sign.go +++ b/cmd/notation/blob/sign.go @@ -122,6 +122,7 @@ Example - Sign a blob artifact with timestamping: command.Flags().StringVar(&opts.tsaServerURL, "timestamp-url", "", "RFC 3161 Timestamping Authority (TSA) server URL") command.Flags().StringVar(&opts.tsaRootCertificatePath, "timestamp-root-cert", "", "filepath of timestamp authority root certificate") command.Flags().BoolVar(&opts.force, "force", false, "override the existing signature file, never prompt") + command.MarkFlagsRequiredTogether("timestamp-url", "timestamp-root-cert") return command } diff --git a/test/e2e/suite/command/blob/sign.go b/test/e2e/suite/command/blob/sign.go index 6cb10624d..daea4d57e 100644 --- a/test/e2e/suite/command/blob/sign.go +++ b/test/e2e/suite/command/blob/sign.go @@ -14,146 +14,159 @@ package blob import ( + "fmt" + "path/filepath" + . "github.com/notaryproject/notation/test/e2e/internal/notation" "github.com/notaryproject/notation/test/e2e/internal/utils" . "github.com/notaryproject/notation/test/e2e/suite/common" . "github.com/onsi/ginkgo/v2" ) +const tsaURL = "http://timestamp.digicert.com" + var _ = Describe("notation blob sign", func() { - It("blob sign", func() { + It("with blob sign", func() { HostWithBlob(BaseOptions(), func(notation *utils.ExecOpts, blobPath string, vhost *utils.VirtualHost) { notation.Exec("blob", "sign", blobPath). - MatchKeyWords(SignSuccessfully) + MatchKeyWords(SignSuccessfully). + MatchKeyWords("Signature file written to") }) }) It("with COSE format", func() { HostWithBlob(BaseOptions(), func(notation *utils.ExecOpts, blobPath string, vhost *utils.VirtualHost) { notation.Exec("blob", "sign", "--signature-format", "cose", blobPath). + MatchKeyWords(SignSuccessfully). + MatchKeyWords("Signature file written to") + }) + }) + + It("with specific key", func() { + HostWithBlob(BaseOptions(), func(notation *utils.ExecOpts, blobPath string, vhost *utils.VirtualHost) { + const keyName = "sKey" + notation.Exec("cert", "generate-test", keyName). + MatchKeyWords(fmt.Sprintf("notation/localkeys/%s.crt", keyName)) + + notation.Exec("blob", "sign", "--key", keyName, blobPath). + MatchKeyWords(SignSuccessfully). + MatchKeyWords("Signature file written to") + }) + }) + + It("with invalid key", func() { + HostWithBlob(BaseOptions(), func(notation *utils.ExecOpts, blobPath string, vhost *utils.VirtualHost) { + notation.ExpectFailure().Exec("blob", "sign", "--key", "invalid", blobPath). + MatchErrKeyWords("signing key not found") + }) + }) + + It("with expiry in 24h", func() { + HostWithBlob(BaseOptions(), func(notation *utils.ExecOpts, blobPath string, vhost *utils.VirtualHost) { + notation.Exec("blob", "sign", "--expiry", "24h", blobPath). + MatchKeyWords(SignSuccessfully). + MatchKeyWords("Signature file written to") + }) + }) + + It("with signature directory", func() { + HostWithBlob(BaseOptions(), func(notation *utils.ExecOpts, blobPath string, vhost *utils.VirtualHost) { + notation.Exec("blob", "sign", "--signature-directory", blobPath, blobPath). + MatchKeyWords(SignSuccessfully). + MatchKeyWords(fmt.Sprintf("Signature file written to %q", blobPath)) + }) + }) + + It("with force saving signature", func() { + HostWithBlob(BaseOptions(), func(notation *utils.ExecOpts, blobPath string, vhost *utils.VirtualHost) { + notation.Exec("blob", "sign", blobPath). MatchKeyWords(SignSuccessfully) + + notation.Exec("blob", "sign", "--force", blobPath). + MatchErrKeyWords("Warning: existing signature file will be overwritten"). + MatchKeyWords(SignSuccessfully). + MatchKeyWords("Signature file written to") }) }) - // It("with specific key", func() { - // Host(BaseOptions(), func(notation *utils.ExecOpts, artifact *Artifact, vhost *utils.VirtualHost) { - // const keyName = "sKey" - // notation.Exec("cert", "generate-test", keyName). - // MatchKeyWords(fmt.Sprintf("notation/localkeys/%s.crt", keyName)) - - // notation.Exec("sign", "--key", keyName, artifact.ReferenceWithDigest()). - // MatchKeyWords(SignSuccessfully) - - // // copy the generated cert file and create the new trust policy for verify signature with generated new key. - // OldNotation(AuthOption("", ""), - // AddTrustStoreOption(keyName, vhost.AbsolutePath(NotationDirName, LocalKeysDirName, keyName+".crt")), - // AddTrustPolicyOption("generate_test_trustpolicy.json"), - // ).Exec("verify", artifact.ReferenceWithTag()). - // MatchKeyWords(VerifySuccessfully) - // }) - // }) - - // It("with expiry in 24h", func() { - // Host(BaseOptions(), func(notation *utils.ExecOpts, artifact *Artifact, vhost *utils.VirtualHost) { - // notation.Exec("sign", "--expiry", "24h", artifact.ReferenceWithDigest()). - // MatchKeyWords(SignSuccessfully) - - // OldNotation().Exec("verify", artifact.ReferenceWithTag()). - // MatchKeyWords(VerifySuccessfully) - // }) - // }) - - // It("with expiry in 2s", func() { - // Host(BaseOptions(), func(notation *utils.ExecOpts, artifact *Artifact, vhost *utils.VirtualHost) { - // notation.Exec("sign", "--expiry", "2s", artifact.ReferenceWithDigest()). - // MatchKeyWords(SignSuccessfully) - - // // sleep to wait for expiry - // time.Sleep(2100 * time.Millisecond) - - // OldNotation().ExpectFailure().Exec("verify", artifact.ReferenceWithDigest(), "-v"). - // MatchErrKeyWords("expiry validation failed."). - // MatchErrKeyWords("signature verification failed for all the signatures") - // }) - // }) - - // It("with timestamping", func() { - // Host(BaseOptions(), func(notation *utils.ExecOpts, artifact *Artifact, vhost *utils.VirtualHost) { - // notation.Exec("sign", "--timestamp-url", "http://rfc3161timestamp.globalsign.com/advanced", "--timestamp-root-cert", filepath.Join(NotationE2EConfigPath, "timestamp", "globalsignTSARoot.cer"), artifact.ReferenceWithDigest()). - // MatchKeyWords(SignSuccessfully) - // }) - // }) - - // It("with timestamp-root-cert but no timestamp-url", func() { - // Host(BaseOptions(), func(notation *utils.ExecOpts, artifact *Artifact, vhost *utils.VirtualHost) { - // notation.ExpectFailure().Exec("sign", "--timestamp-root-cert", filepath.Join(NotationE2EConfigPath, "timestamp", "globalsignTSARoot.cer"), artifact.ReferenceWithDigest()). - // MatchErrKeyWords("Error: if any flags in the group [timestamp-url timestamp-root-cert] are set they must all be set; missing [timestamp-url]") - // }) - // }) - - // It("with timestamp-url but no timestamp-root-cert", func() { - // Host(BaseOptions(), func(notation *utils.ExecOpts, artifact *Artifact, vhost *utils.VirtualHost) { - // notation.ExpectFailure().Exec("sign", "--timestamp-url", "http://rfc3161timestamp.globalsign.com/advanced", artifact.ReferenceWithDigest()). - // MatchErrKeyWords("Error: if any flags in the group [timestamp-url timestamp-root-cert] are set they must all be set; missing [timestamp-root-cert]") - // }) - // }) - - // It("with timestamping and empty tsa server", func() { - // Host(BaseOptions(), func(notation *utils.ExecOpts, artifact *Artifact, vhost *utils.VirtualHost) { - // notation.ExpectFailure().Exec("sign", "--timestamp-url", "", "--timestamp-root-cert", filepath.Join(NotationE2EConfigPath, "timestamp", "globalsignTSARoot.cer"), artifact.ReferenceWithDigest()). - // MatchErrKeyWords("Error: timestamping: tsa url cannot be empty") - // }) - // }) - - // It("with timestamping and empty tsa root cert", func() { - // Host(BaseOptions(), func(notation *utils.ExecOpts, artifact *Artifact, vhost *utils.VirtualHost) { - // notation.ExpectFailure().Exec("sign", "--timestamp-url", "dummy", "--timestamp-root-cert", "", artifact.ReferenceWithDigest()). - // MatchErrKeyWords("Error: timestamping: tsa root certificate path cannot be empty") - // }) - // }) - - // It("with timestamping and invalid tsa server", func() { - // Host(BaseOptions(), func(notation *utils.ExecOpts, artifact *Artifact, vhost *utils.VirtualHost) { - // notation.ExpectFailure().Exec("sign", "--timestamp-url", "http://invalid.com", "--timestamp-root-cert", filepath.Join(NotationE2EConfigPath, "timestamp", "globalsignTSARoot.cer"), artifact.ReferenceWithDigest()). - // MatchErrKeyWords("Error: timestamp: Post \"http://invalid.com\""). - // MatchErrKeyWords("server misbehaving") - // }) - // }) - - // It("with timestamping and invalid tsa root certificate", func() { - // Host(BaseOptions(), func(notation *utils.ExecOpts, artifact *Artifact, vhost *utils.VirtualHost) { - // notation.ExpectFailure().Exec("sign", "--timestamp-url", "http://timestamp.digicert.com", "--timestamp-root-cert", filepath.Join(NotationE2EConfigPath, "timestamp", "invalid.crt"), artifact.ReferenceWithDigest()). - // MatchErrKeyWords("Error: x509: malformed certificate") - // }) - // }) - - // It("with timestamping and empty tsa root certificate file", func() { - // Host(BaseOptions(), func(notation *utils.ExecOpts, artifact *Artifact, vhost *utils.VirtualHost) { - // notation.ExpectFailure().Exec("sign", "--timestamp-url", "http://timestamp.digicert.com", "--timestamp-root-cert", filepath.Join(NotationE2EConfigPath, "timestamp", "Empty.txt"), artifact.ReferenceWithDigest()). - // MatchErrKeyWords("cannot find any certificate from"). - // MatchErrKeyWords("Expecting single x509 root certificate in PEM or DER format from the file") - // }) - // }) - - // It("with timestamping and more than one certificates in tsa root certificate file", func() { - // Host(BaseOptions(), func(notation *utils.ExecOpts, artifact *Artifact, vhost *utils.VirtualHost) { - // notation.ExpectFailure().Exec("sign", "--timestamp-url", "http://timestamp.digicert.com", "--timestamp-root-cert", filepath.Join(NotationE2EConfigPath, "timestamp", "CertChain.pem"), artifact.ReferenceWithDigest()). - // MatchErrKeyWords("found more than one certificates"). - // MatchErrKeyWords("Expecting single x509 root certificate in PEM or DER format from the file") - // }) - // }) - - // It("with timestamping and intermediate certificate file", func() { - // Host(BaseOptions(), func(notation *utils.ExecOpts, artifact *Artifact, vhost *utils.VirtualHost) { - // notation.ExpectFailure().Exec("sign", "--timestamp-url", "http://timestamp.digicert.com", "--timestamp-root-cert", filepath.Join(NotationE2EConfigPath, "timestamp", "intermediate.pem"), artifact.ReferenceWithDigest()). - // MatchErrKeyWords("failed to check root certificate with error: crypto/rsa: verification error") - // }) - // }) - - // It("with timestamping and not self-issued certificate file", func() { - // Host(BaseOptions(), func(notation *utils.ExecOpts, artifact *Artifact, vhost *utils.VirtualHost) { - // notation.ExpectFailure().Exec("sign", "--timestamp-url", "http://timestamp.digicert.com", "--timestamp-root-cert", filepath.Join(NotationE2EConfigPath, "timestamp", "notSelfIssued.crt"), artifact.ReferenceWithDigest()). - // MatchErrKeyWords("is not a root certificate. Expecting single x509 root certificate in PEM or DER format from the file") - // }) - // }) + It("with timestamping", func() { + HostWithBlob(BaseOptions(), func(notation *utils.ExecOpts, blobPath string, vhost *utils.VirtualHost) { + notation.Exec("blob", "sign", "--timestamp-url", tsaURL, "--timestamp-root-cert", filepath.Join(NotationE2EConfigPath, "timestamp", "DigiCertTSARootSHA384.cer"), blobPath). + MatchKeyWords(SignSuccessfully). + MatchKeyWords("Signature file written to") + }) + }) + + It("with timestamp-root-cert but no timestamp-url", func() { + HostWithBlob(BaseOptions(), func(notation *utils.ExecOpts, blobPath string, vhost *utils.VirtualHost) { + notation.ExpectFailure().Exec("blob", "sign", "--timestamp-root-cert", filepath.Join(NotationE2EConfigPath, "timestamp", "DigiCertTSARootSHA384.cer"), blobPath). + MatchErrKeyWords("Error: if any flags in the group [timestamp-url timestamp-root-cert] are set they must all be set; missing [timestamp-url]") + }) + }) + + It("with timestamp-url but no timestamp-root-cert", func() { + HostWithBlob(BaseOptions(), func(notation *utils.ExecOpts, blobPath string, vhost *utils.VirtualHost) { + notation.ExpectFailure().Exec("blob", "sign", "--timestamp-url", tsaURL, blobPath). + MatchErrKeyWords("Error: if any flags in the group [timestamp-url timestamp-root-cert] are set they must all be set; missing [timestamp-root-cert]") + }) + }) + + It("with timestamping and empty tsa server", func() { + HostWithBlob(BaseOptions(), func(notation *utils.ExecOpts, blobPath string, vhost *utils.VirtualHost) { + notation.ExpectFailure().Exec("blob", "sign", "--timestamp-url", "", "--timestamp-root-cert", filepath.Join(NotationE2EConfigPath, "timestamp", "DigiCertTSARootSHA384.cer"), blobPath). + MatchErrKeyWords("Error: timestamping: tsa url cannot be empty") + }) + }) + + It("with timestamping and empty tsa root cert", func() { + HostWithBlob(BaseOptions(), func(notation *utils.ExecOpts, blobPath string, vhost *utils.VirtualHost) { + notation.ExpectFailure().Exec("blob", "sign", "--timestamp-url", "dummy", "--timestamp-root-cert", "", blobPath). + MatchErrKeyWords("Error: timestamping: tsa root certificate path cannot be empty") + }) + }) + + It("with timestamping and invalid tsa server", func() { + HostWithBlob(BaseOptions(), func(notation *utils.ExecOpts, blobPath string, vhost *utils.VirtualHost) { + notation.ExpectFailure().Exec("blob", "sign", "--timestamp-url", "http://tsa.invalid", "--timestamp-root-cert", filepath.Join(NotationE2EConfigPath, "timestamp", "DigiCertTSARootSHA384.cer"), blobPath). + MatchErrKeyWords("Error: timestamp: Post \"http://tsa.invalid\""). + MatchErrKeyWords("server misbehaving") + }) + }) + + It("with timestamping and invalid tsa root certificate", func() { + HostWithBlob(BaseOptions(), func(notation *utils.ExecOpts, blobPath string, vhost *utils.VirtualHost) { + notation.ExpectFailure().Exec("blob", "sign", "--timestamp-url", tsaURL, "--timestamp-root-cert", filepath.Join(NotationE2EConfigPath, "timestamp", "invalid.crt"), blobPath). + MatchErrKeyWords("Error: x509: malformed certificate") + }) + }) + + It("with timestamping and empty tsa root certificate file", func() { + HostWithBlob(BaseOptions(), func(notation *utils.ExecOpts, blobPath string, vhost *utils.VirtualHost) { + notation.ExpectFailure().Exec("blob", "sign", "--timestamp-url", tsaURL, "--timestamp-root-cert", filepath.Join(NotationE2EConfigPath, "timestamp", "Empty.txt"), blobPath). + MatchErrKeyWords("cannot find any certificate from"). + MatchErrKeyWords("Expecting single x509 root certificate in PEM or DER format from the file") + }) + }) + + It("with timestamping and more than one certificates in tsa root certificate file", func() { + HostWithBlob(BaseOptions(), func(notation *utils.ExecOpts, blobPath string, vhost *utils.VirtualHost) { + notation.ExpectFailure().Exec("blob", "sign", "--timestamp-url", tsaURL, "--timestamp-root-cert", filepath.Join(NotationE2EConfigPath, "timestamp", "CertChain.pem"), blobPath). + MatchErrKeyWords("found more than one certificates"). + MatchErrKeyWords("Expecting single x509 root certificate in PEM or DER format from the file") + }) + }) + + It("with timestamping and intermediate certificate file", func() { + HostWithBlob(BaseOptions(), func(notation *utils.ExecOpts, blobPath string, vhost *utils.VirtualHost) { + notation.ExpectFailure().Exec("blob", "sign", "--timestamp-url", tsaURL, "--timestamp-root-cert", filepath.Join(NotationE2EConfigPath, "timestamp", "intermediate.pem"), blobPath). + MatchErrKeyWords("failed to check root certificate with error: crypto/rsa: verification error") + }) + }) + + It("with timestamping and not self-issued certificate file", func() { + HostWithBlob(BaseOptions(), func(notation *utils.ExecOpts, blobPath string, vhost *utils.VirtualHost) { + notation.ExpectFailure().Exec("blob", "sign", "--timestamp-url", tsaURL, "--timestamp-root-cert", filepath.Join(NotationE2EConfigPath, "timestamp", "notSelfIssued.crt"), blobPath). + MatchErrKeyWords("is not a root certificate. Expecting single x509 root certificate in PEM or DER format from the file") + }) + }) }) diff --git a/test/e2e/suite/command/sign.go b/test/e2e/suite/command/sign.go index 334e27e25..7cab31b4e 100644 --- a/test/e2e/suite/command/sign.go +++ b/test/e2e/suite/command/sign.go @@ -296,8 +296,8 @@ var _ = Describe("notation sign", func() { It("with timestamping and invalid tsa server", func() { Host(BaseOptions(), func(notation *utils.ExecOpts, artifact *Artifact, vhost *utils.VirtualHost) { - notation.ExpectFailure().Exec("sign", "--timestamp-url", "http://invalid.com", "--timestamp-root-cert", filepath.Join(NotationE2EConfigPath, "timestamp", "globalsignTSARoot.cer"), artifact.ReferenceWithDigest()). - MatchErrKeyWords("Error: timestamp: Post \"http://invalid.com\""). + notation.ExpectFailure().Exec("sign", "--timestamp-url", "http://tsa.invalid", "--timestamp-root-cert", filepath.Join(NotationE2EConfigPath, "timestamp", "globalsignTSARoot.cer"), artifact.ReferenceWithDigest()). + MatchErrKeyWords("Error: timestamp: Post \"http://tsa.invalid\""). MatchErrKeyWords("server misbehaving") }) }) From 07d0ffbc405182781f5978dcdf9d7a7a06154de4 Mon Sep 17 00:00:00 2001 From: Patrick Zheng Date: Thu, 26 Dec 2024 17:42:00 +0800 Subject: [PATCH 09/53] add e2e tests Signed-off-by: Patrick Zheng --- test/e2e/suite/command/blob/sign.go | 3 +-- test/e2e/suite/command/sign.go | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/test/e2e/suite/command/blob/sign.go b/test/e2e/suite/command/blob/sign.go index daea4d57e..e4f70941b 100644 --- a/test/e2e/suite/command/blob/sign.go +++ b/test/e2e/suite/command/blob/sign.go @@ -128,8 +128,7 @@ var _ = Describe("notation blob sign", func() { It("with timestamping and invalid tsa server", func() { HostWithBlob(BaseOptions(), func(notation *utils.ExecOpts, blobPath string, vhost *utils.VirtualHost) { notation.ExpectFailure().Exec("blob", "sign", "--timestamp-url", "http://tsa.invalid", "--timestamp-root-cert", filepath.Join(NotationE2EConfigPath, "timestamp", "DigiCertTSARootSHA384.cer"), blobPath). - MatchErrKeyWords("Error: timestamp: Post \"http://tsa.invalid\""). - MatchErrKeyWords("server misbehaving") + MatchErrKeyWords("Error: timestamp: Post \"http://tsa.invalid\"") }) }) diff --git a/test/e2e/suite/command/sign.go b/test/e2e/suite/command/sign.go index 7cab31b4e..d9cefd445 100644 --- a/test/e2e/suite/command/sign.go +++ b/test/e2e/suite/command/sign.go @@ -297,8 +297,7 @@ var _ = Describe("notation sign", func() { It("with timestamping and invalid tsa server", func() { Host(BaseOptions(), func(notation *utils.ExecOpts, artifact *Artifact, vhost *utils.VirtualHost) { notation.ExpectFailure().Exec("sign", "--timestamp-url", "http://tsa.invalid", "--timestamp-root-cert", filepath.Join(NotationE2EConfigPath, "timestamp", "globalsignTSARoot.cer"), artifact.ReferenceWithDigest()). - MatchErrKeyWords("Error: timestamp: Post \"http://tsa.invalid\""). - MatchErrKeyWords("server misbehaving") + MatchErrKeyWords("Error: timestamp: Post \"http://tsa.invalid\"") }) }) From cf40ba23926101ab73ac8b8da48769536cfe5cbe Mon Sep 17 00:00:00 2001 From: Patrick Zheng Date: Thu, 26 Dec 2024 17:54:20 +0800 Subject: [PATCH 10/53] fix e2e tests Signed-off-by: Patrick Zheng --- cmd/notation/blob/sign.go | 9 +++++++++ test/e2e/suite/command/blob/sign.go | 13 +++++-------- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/cmd/notation/blob/sign.go b/cmd/notation/blob/sign.go index 22ead3697..b280bd541 100644 --- a/cmd/notation/blob/sign.go +++ b/cmd/notation/blob/sign.go @@ -109,6 +109,15 @@ Example - Sign a blob artifact with timestamping: return nil }, RunE: func(cmd *cobra.Command, args []string) error { + // timestamping + if cmd.Flags().Changed("timestamp-url") { + if opts.tsaServerURL == "" { + return errors.New("timestamping: tsa url cannot be empty") + } + if opts.tsaRootCertificatePath == "" { + return errors.New("timestamping: tsa root certificate path cannot be empty") + } + } return runBlobSign(cmd, opts) }, } diff --git a/test/e2e/suite/command/blob/sign.go b/test/e2e/suite/command/blob/sign.go index e4f70941b..e6c376e79 100644 --- a/test/e2e/suite/command/blob/sign.go +++ b/test/e2e/suite/command/blob/sign.go @@ -48,7 +48,7 @@ var _ = Describe("notation blob sign", func() { notation.Exec("cert", "generate-test", keyName). MatchKeyWords(fmt.Sprintf("notation/localkeys/%s.crt", keyName)) - notation.Exec("blob", "sign", "--key", keyName, blobPath). + notation.Exec("blob", "sign", "--force", "--key", keyName, blobPath). MatchKeyWords(SignSuccessfully). MatchKeyWords("Signature file written to") }) @@ -63,7 +63,7 @@ var _ = Describe("notation blob sign", func() { It("with expiry in 24h", func() { HostWithBlob(BaseOptions(), func(notation *utils.ExecOpts, blobPath string, vhost *utils.VirtualHost) { - notation.Exec("blob", "sign", "--expiry", "24h", blobPath). + notation.Exec("blob", "sign", "--expiry", "24h", "--force", blobPath). MatchKeyWords(SignSuccessfully). MatchKeyWords("Signature file written to") }) @@ -71,17 +71,14 @@ var _ = Describe("notation blob sign", func() { It("with signature directory", func() { HostWithBlob(BaseOptions(), func(notation *utils.ExecOpts, blobPath string, vhost *utils.VirtualHost) { - notation.Exec("blob", "sign", "--signature-directory", blobPath, blobPath). + notation.Exec("blob", "sign", "--signature-directory", filepath.Dir(blobPath), blobPath). MatchKeyWords(SignSuccessfully). - MatchKeyWords(fmt.Sprintf("Signature file written to %q", blobPath)) + MatchKeyWords(fmt.Sprintf("Signature file written to %q", filepath.Dir(blobPath))) }) }) It("with force saving signature", func() { HostWithBlob(BaseOptions(), func(notation *utils.ExecOpts, blobPath string, vhost *utils.VirtualHost) { - notation.Exec("blob", "sign", blobPath). - MatchKeyWords(SignSuccessfully) - notation.Exec("blob", "sign", "--force", blobPath). MatchErrKeyWords("Warning: existing signature file will be overwritten"). MatchKeyWords(SignSuccessfully). @@ -91,7 +88,7 @@ var _ = Describe("notation blob sign", func() { It("with timestamping", func() { HostWithBlob(BaseOptions(), func(notation *utils.ExecOpts, blobPath string, vhost *utils.VirtualHost) { - notation.Exec("blob", "sign", "--timestamp-url", tsaURL, "--timestamp-root-cert", filepath.Join(NotationE2EConfigPath, "timestamp", "DigiCertTSARootSHA384.cer"), blobPath). + notation.Exec("blob", "sign", "--force", "--timestamp-url", tsaURL, "--timestamp-root-cert", filepath.Join(NotationE2EConfigPath, "timestamp", "DigiCertTSARootSHA384.cer"), blobPath). MatchKeyWords(SignSuccessfully). MatchKeyWords("Signature file written to") }) From 1051ad40e29c37d7796fc60358b2a2ac98c8e7e9 Mon Sep 17 00:00:00 2001 From: Patrick Zheng Date: Thu, 26 Dec 2024 17:58:57 +0800 Subject: [PATCH 11/53] fix e2e test Signed-off-by: Patrick Zheng --- test/e2e/suite/command/blob/sign.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/test/e2e/suite/command/blob/sign.go b/test/e2e/suite/command/blob/sign.go index e6c376e79..041a6a072 100644 --- a/test/e2e/suite/command/blob/sign.go +++ b/test/e2e/suite/command/blob/sign.go @@ -71,9 +71,10 @@ var _ = Describe("notation blob sign", func() { It("with signature directory", func() { HostWithBlob(BaseOptions(), func(notation *utils.ExecOpts, blobPath string, vhost *utils.VirtualHost) { - notation.Exec("blob", "sign", "--signature-directory", filepath.Dir(blobPath), blobPath). + blobDir := filepath.Dir(blobPath) + notation.Exec("blob", "sign", "--signature-directory", blobDir, blobPath). MatchKeyWords(SignSuccessfully). - MatchKeyWords(fmt.Sprintf("Signature file written to %q", filepath.Dir(blobPath))) + MatchKeyWords(fmt.Sprintf("Signature file written to %q", filepath.Join(blobDir, "blobFile.jws.sig"))) }) }) From cf5fecb40869bdd877698b1ad2b145e60d1d8c99 Mon Sep 17 00:00:00 2001 From: Patrick Zheng Date: Fri, 27 Dec 2024 09:16:53 +0800 Subject: [PATCH 12/53] add more e2e tests Signed-off-by: Patrick Zheng --- test/e2e/suite/command/blob/sign.go | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/test/e2e/suite/command/blob/sign.go b/test/e2e/suite/command/blob/sign.go index 041a6a072..8e3e3781b 100644 --- a/test/e2e/suite/command/blob/sign.go +++ b/test/e2e/suite/command/blob/sign.go @@ -42,6 +42,14 @@ var _ = Describe("notation blob sign", func() { }) }) + It("with specified media-type", func() { + HostWithBlob(BaseOptions(), func(notation *utils.ExecOpts, blobPath string, vhost *utils.VirtualHost) { + notation.Exec("blob", "sign", "--force", "--media-type", "other-media-type", blobPath). + MatchKeyWords(SignSuccessfully). + MatchKeyWords("Signature file written to") + }) + }) + It("with specific key", func() { HostWithBlob(BaseOptions(), func(notation *utils.ExecOpts, blobPath string, vhost *utils.VirtualHost) { const keyName = "sKey" @@ -87,6 +95,14 @@ var _ = Describe("notation blob sign", func() { }) }) + It("with user metadata", func() { + HostWithBlob(BaseOptions(), func(notation *utils.ExecOpts, blobPath string, vhost *utils.VirtualHost) { + notation.Exec("blob", "sign", "--force", "--user-metadata", "k1=v1", "--user-metadata", "k2=v2", blobPath). + MatchKeyWords(SignSuccessfully). + MatchKeyWords("Signature file written to") + }) + }) + It("with timestamping", func() { HostWithBlob(BaseOptions(), func(notation *utils.ExecOpts, blobPath string, vhost *utils.VirtualHost) { notation.Exec("blob", "sign", "--force", "--timestamp-url", tsaURL, "--timestamp-root-cert", filepath.Join(NotationE2EConfigPath, "timestamp", "DigiCertTSARootSHA384.cer"), blobPath). From fc4dcfc09c3a655ea649251ea14526c473557b47 Mon Sep 17 00:00:00 2001 From: Patrick Zheng Date: Fri, 27 Dec 2024 09:58:29 +0800 Subject: [PATCH 13/53] add more e2e tests Signed-off-by: Patrick Zheng --- test/e2e/suite/command/blob/sign.go | 64 +++++++++++++++++++++++++---- 1 file changed, 57 insertions(+), 7 deletions(-) diff --git a/test/e2e/suite/command/blob/sign.go b/test/e2e/suite/command/blob/sign.go index 8e3e3781b..645ee5b5e 100644 --- a/test/e2e/suite/command/blob/sign.go +++ b/test/e2e/suite/command/blob/sign.go @@ -15,6 +15,7 @@ package blob import ( "fmt" + "os" "path/filepath" . "github.com/notaryproject/notation/test/e2e/internal/notation" @@ -26,6 +27,7 @@ import ( const tsaURL = "http://timestamp.digicert.com" var _ = Describe("notation blob sign", func() { + // Success cases It("with blob sign", func() { HostWithBlob(BaseOptions(), func(notation *utils.ExecOpts, blobPath string, vhost *utils.VirtualHost) { notation.Exec("blob", "sign", blobPath). @@ -62,13 +64,6 @@ var _ = Describe("notation blob sign", func() { }) }) - It("with invalid key", func() { - HostWithBlob(BaseOptions(), func(notation *utils.ExecOpts, blobPath string, vhost *utils.VirtualHost) { - notation.ExpectFailure().Exec("blob", "sign", "--key", "invalid", blobPath). - MatchErrKeyWords("signing key not found") - }) - }) - It("with expiry in 24h", func() { HostWithBlob(BaseOptions(), func(notation *utils.ExecOpts, blobPath string, vhost *utils.VirtualHost) { notation.Exec("blob", "sign", "--expiry", "24h", "--force", blobPath). @@ -111,6 +106,61 @@ var _ = Describe("notation blob sign", func() { }) }) + // Failure cases + It("with undefined signature format", func() { + HostWithBlob(BaseOptions(), func(notation *utils.ExecOpts, blobPath string, vhost *utils.VirtualHost) { + notation.ExpectFailure().Exec("blob", "sign", "--signature-format", "invalid", blobPath). + MatchErrKeyWords(`signature format "invalid" not supported`) + }) + }) + + It("with invalid key", func() { + HostWithBlob(BaseOptions(), func(notation *utils.ExecOpts, blobPath string, vhost *utils.VirtualHost) { + notation.ExpectFailure().Exec("blob", "sign", "--key", "invalid", blobPath). + MatchErrKeyWords("signing key not found") + }) + }) + + It("with invalid plugin-config", func() { + HostWithBlob(BaseOptions(), func(notation *utils.ExecOpts, blobPath string, vhost *utils.VirtualHost) { + notation.ExpectFailure().Exec("blob", "sign", "--plugin-config", "invalid", blobPath). + MatchErrKeyWords(`could not parse flag plugin-config: key-value pair requires "=" as separator`) + }) + }) + + It("with invalid user metadata", func() { + HostWithBlob(BaseOptions(), func(notation *utils.ExecOpts, blobPath string, vhost *utils.VirtualHost) { + notation.ExpectFailure().Exec("blob", "sign", "--user-metadata", "invalid", blobPath). + MatchErrKeyWords(`could not parse flag user-metadata: key-value pair requires "=" as separator`) + }) + }) + + It("with no permission to read the blob file", func() { + HostWithBlob(BaseOptions(), func(notation *utils.ExecOpts, blobPath string, vhost *utils.VirtualHost) { + if err := os.Chmod(blobPath, 0000); err != nil { + Fail(err.Error()) + } + defer os.Chmod(blobPath, 0700) + + notation.ExpectFailure().Exec("blob", "sign", blobPath). + MatchErrKeyWords("permission denied") + }) + }) + + It("with no permission to write the signature file", func() { + HostWithBlob(BaseOptions(), func(notation *utils.ExecOpts, blobPath string, vhost *utils.VirtualHost) { + blobDir := filepath.Dir(blobPath) + sigPath := filepath.Join(blobDir, "blobFile.jws.sig") + if err := os.MkdirAll(sigPath, 0000); err != nil { + Fail(err.Error()) + } + defer os.Chmod(sigPath, 0700) + + notation.ExpectFailure().Exec("blob", "sign", "--force", blobPath). + MatchErrKeyWords("permission denied") + }) + }) + It("with timestamp-root-cert but no timestamp-url", func() { HostWithBlob(BaseOptions(), func(notation *utils.ExecOpts, blobPath string, vhost *utils.VirtualHost) { notation.ExpectFailure().Exec("blob", "sign", "--timestamp-root-cert", filepath.Join(NotationE2EConfigPath, "timestamp", "DigiCertTSARootSHA384.cer"), blobPath). From 576a286440d3179641172ae520f96b24f04e8630 Mon Sep 17 00:00:00 2001 From: Patrick Zheng Date: Fri, 27 Dec 2024 10:09:47 +0800 Subject: [PATCH 14/53] fix e2e test Signed-off-by: Patrick Zheng --- test/e2e/suite/command/blob/sign.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/e2e/suite/command/blob/sign.go b/test/e2e/suite/command/blob/sign.go index 645ee5b5e..f9325c955 100644 --- a/test/e2e/suite/command/blob/sign.go +++ b/test/e2e/suite/command/blob/sign.go @@ -150,13 +150,13 @@ var _ = Describe("notation blob sign", func() { It("with no permission to write the signature file", func() { HostWithBlob(BaseOptions(), func(notation *utils.ExecOpts, blobPath string, vhost *utils.VirtualHost) { blobDir := filepath.Dir(blobPath) - sigPath := filepath.Join(blobDir, "blobFile.jws.sig") - if err := os.MkdirAll(sigPath, 0000); err != nil { + sigDir := filepath.Join(blobDir, "signature") + if err := os.MkdirAll(sigDir, 0000); err != nil { Fail(err.Error()) } - defer os.Chmod(sigPath, 0700) + defer os.Chmod(sigDir, 0700) - notation.ExpectFailure().Exec("blob", "sign", "--force", blobPath). + notation.ExpectFailure().Exec("blob", "sign", "--signature-directory", sigDir, blobPath). MatchErrKeyWords("permission denied") }) }) From 0c984a01cf11ada5b080b68226ea5197b44a54f7 Mon Sep 17 00:00:00 2001 From: Patrick Zheng Date: Fri, 27 Dec 2024 10:18:11 +0800 Subject: [PATCH 15/53] add more tests Signed-off-by: Patrick Zheng --- internal/cmd/signer_test.go | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/internal/cmd/signer_test.go b/internal/cmd/signer_test.go index fc8aadfc1..a71248904 100644 --- a/internal/cmd/signer_test.go +++ b/internal/cmd/signer_test.go @@ -45,7 +45,34 @@ func TestPluginSignerImpl(t *testing.T) { } } -func TestGetSigner(t *testing.T) { +func TestGetSignerFromOpts(t *testing.T) { + if runtime.GOOS == "windows" { + t.Skip("skipping test on Windows") + } + + defer func(oldLibexeDir string) { + dir.UserLibexecDir = oldLibexeDir + }(dir.UserLibexecDir) + + dir.UserLibexecDir = "./testdata/plugins" + ctx := context.Background() + opts := &SignerFlagOpts{ + KeyID: "testKeyId", + PluginName: "testPlugin", + } + + _, err := GetSigner(ctx, opts) + if err != nil { + t.Fatalf("expected nil error, but got %s", err) + } + + _, err = GetBlobSigner(ctx, opts) + if err != nil { + t.Fatalf("expected nil error, but got %s", err) + } +} + +func TestGetSignerFromConfig(t *testing.T) { if runtime.GOOS == "windows" { t.Skip("skipping test on Windows") } From 14a1f3e9450c81c0fb97e7e0a577b3aa11fe9cc0 Mon Sep 17 00:00:00 2001 From: Patrick Zheng Date: Tue, 31 Dec 2024 11:20:24 +0800 Subject: [PATCH 16/53] update Signed-off-by: Patrick Zheng --- cmd/notation/blob/sign.go | 10 ++++----- internal/cmd/signer.go | 33 ++++++----------------------- internal/cmd/signer_test.go | 33 +++++++++++++++++------------ specs/commandline/blob.md | 18 ++++++++-------- test/e2e/suite/command/blob/sign.go | 15 +++---------- 5 files changed, 42 insertions(+), 67 deletions(-) diff --git a/cmd/notation/blob/sign.go b/cmd/notation/blob/sign.go index b280bd541..023c00779 100644 --- a/cmd/notation/blob/sign.go +++ b/cmd/notation/blob/sign.go @@ -139,7 +139,7 @@ func runBlobSign(command *cobra.Command, cmdOpts *blobSignOpts) error { // set log level ctx := cmdOpts.LoggingFlagOpts.InitializeLogger(command.Context()) - signer, err := cmd.GetBlobSigner(ctx, &cmdOpts.SignerFlagOpts) + blobSigner, err := cmd.GetSigner(ctx, &cmdOpts.SignerFlagOpts) if err != nil { return err } @@ -154,12 +154,12 @@ func runBlobSign(command *cobra.Command, cmdOpts *blobSignOpts) error { defer blobFile.Close() // core process - sig, _, err := notation.SignBlob(ctx, signer, blobFile, blobOpts) + sig, _, err := notation.SignBlob(ctx, blobSigner, blobFile, blobOpts) if err != nil { return err } signaturePath := signatureFilepath(cmdOpts.signatureDirectory, cmdOpts.blobPath, cmdOpts.SignatureFormat) - fmt.Printf("Writing signature to file %q...\n", signaturePath) + fmt.Printf("Writing signature to file %s\n", signaturePath) // optional confirmation if !cmdOpts.force { @@ -180,8 +180,8 @@ func runBlobSign(command *cobra.Command, cmdOpts *blobSignOpts) error { if err := osutil.WriteFile(signaturePath, sig); err != nil { return fmt.Errorf("failed to write signature to file: %w", err) } - fmt.Printf("Successfully signed %q\n ", cmdOpts.blobPath) - fmt.Printf("Signature file written to %q\n", signaturePath) + fmt.Printf("Successfully signed %s\n ", cmdOpts.blobPath) + fmt.Printf("Signature file written to %s\n", signaturePath) return nil } diff --git a/internal/cmd/signer.go b/internal/cmd/signer.go index 66ffc9e90..c0c042026 100644 --- a/internal/cmd/signer.go +++ b/internal/cmd/signer.go @@ -24,35 +24,14 @@ import ( "github.com/notaryproject/notation/pkg/configutil" ) -// GetSigner returns a signer according to the CLI context. -func GetSigner(ctx context.Context, opts *SignerFlagOpts) (notation.Signer, error) { - s, err := signerCore(ctx, opts) - if err != nil { - return nil, err - } - - // always true, as signerCore returns either signer.PluginSigner or - // signer.GenericSigner. - notationSigner, _ := s.(notation.Signer) - return notationSigner, nil -} - -// GetBlobSigner returns a blob signer according to the CLI context. -func GetBlobSigner(ctx context.Context, opts *SignerFlagOpts) (notation.BlobSigner, error) { - s, err := signerCore(ctx, opts) - if err != nil { - return nil, err - } - - // always true, as signerCore returns either signer.PluginSigner or - // signer.GenericSigner. - notationBlobSigner, _ := s.(notation.BlobSigner) - return notationBlobSigner, nil +// Signer is embedded with notation.BlobSigner and notation.Signer. +type Signer interface { + notation.BlobSigner + notation.Signer } -// signerCore returns a signer.PluginSigner or signer.GenericSigner based on -// user opts. -func signerCore(ctx context.Context, opts *SignerFlagOpts) (any, error) { +// GetSigner returns a Signer based on user opts. +func GetSigner(ctx context.Context, opts *SignerFlagOpts) (Signer, error) { // Check if using on-demand key if opts.KeyID != "" && opts.PluginName != "" && opts.Key == "" { // Construct a signer from on-demand key diff --git a/internal/cmd/signer_test.go b/internal/cmd/signer_test.go index a71248904..f41b14e0c 100644 --- a/internal/cmd/signer_test.go +++ b/internal/cmd/signer_test.go @@ -66,7 +66,7 @@ func TestGetSignerFromOpts(t *testing.T) { t.Fatalf("expected nil error, but got %s", err) } - _, err = GetBlobSigner(ctx, opts) + _, err = GetSigner(ctx, opts) if err != nil { t.Fatalf("expected nil error, but got %s", err) } @@ -94,7 +94,7 @@ func TestGetSignerFromConfig(t *testing.T) { t.Fatalf("expected nil error, but got %s", err) } - _, err = GetBlobSigner(ctx, opts) + _, err = GetSigner(ctx, opts) if err != nil { t.Fatalf("expected nil error, but got %s", err) } @@ -115,14 +115,9 @@ func TestGetFailed(t *testing.T) { if err == nil { t.Fatal("GetSigner should return an error") } - - _, err = GetBlobSigner(ctx, opts) - if err == nil { - t.Fatal("GetBlobSigner should return an error") - } } -func TestSignerCore(t *testing.T) { +func TestGetSignerFailed(t *testing.T) { ctx := context.Background() defer func(oldLibexeDir, oldConfigDir string) { @@ -130,6 +125,16 @@ func TestSignerCore(t *testing.T) { dir.UserConfigDir = oldConfigDir }(dir.UserLibexecDir, dir.UserConfigDir) + t.Run("get failed", func(t *testing.T) { + opts := &SignerFlagOpts{} + dir.UserLibexecDir = "./testdata/plugins" + dir.UserConfigDir = "./testdata/invalid_signingkeys" + _, err := GetSigner(ctx, opts) + if err == nil { + t.Fatal("GetSigner should return an error") + } + }) + t.Run("invalid plugin name in opts", func(t *testing.T) { if runtime.GOOS == "windows" { t.Skip("skipping test on Windows") @@ -142,7 +147,7 @@ func TestSignerCore(t *testing.T) { PluginName: "invalid", } expectedErrMsg := `plugin executable file is either not found or inaccessible: stat testdata/plugins/plugins/invalid/notation-invalid: no such file or directory` - _, err := signerCore(ctx, opts) + _, err := GetSigner(ctx, opts) if err == nil || err.Error() != expectedErrMsg { t.Fatalf("expected %s, but got %s", expectedErrMsg, err) } @@ -151,7 +156,7 @@ func TestSignerCore(t *testing.T) { t.Run("failed to resolve key", func(t *testing.T) { dir.UserConfigDir = "./testdata/valid_signingkeys" expectedErrMsg := `default signing key not set. Please set default signing key or specify a key name` - _, err := signerCore(ctx, &SignerFlagOpts{}) + _, err := GetSigner(ctx, &SignerFlagOpts{}) if err == nil || err.Error() != expectedErrMsg { t.Fatalf("expected %s, but got %s", expectedErrMsg, err) } @@ -163,7 +168,7 @@ func TestSignerCore(t *testing.T) { opts := &SignerFlagOpts{ Key: "invalid", } - _, err := signerCore(ctx, opts) + _, err := GetSigner(ctx, opts) if err == nil || err.Error() != expectedErrMsg { t.Fatalf("expected %s, but got %s", expectedErrMsg, err) } @@ -175,7 +180,7 @@ func TestSignerCore(t *testing.T) { opts := &SignerFlagOpts{ Key: "test2", } - _, err := signerCore(ctx, opts) + _, err := GetSigner(ctx, opts) if err == nil || err.Error() != expectedErrMsg { t.Fatalf("expected %s, but got %s", expectedErrMsg, err) } @@ -192,7 +197,7 @@ func TestSignerCore(t *testing.T) { opts := &SignerFlagOpts{ Key: "invalidExternal", } - _, err := signerCore(ctx, opts) + _, err := GetSigner(ctx, opts) if err == nil || err.Error() != expectedErrMsg { t.Fatalf("expected %s, but got %s", expectedErrMsg, err) } @@ -204,7 +209,7 @@ func TestSignerCore(t *testing.T) { opts := &SignerFlagOpts{ Key: "empty", } - _, err := signerCore(ctx, opts) + _, err := GetSigner(ctx, opts) if err == nil || err.Error() != expectedErrMsg { t.Fatalf("expected %s, but got %s", expectedErrMsg, err) } diff --git a/specs/commandline/blob.md b/specs/commandline/blob.md index acf40f81e..daeedeb22 100644 --- a/specs/commandline/blob.md +++ b/specs/commandline/blob.md @@ -75,7 +75,7 @@ Produce a detached signature for a given blob. The signature file will be written to the currently working directory with file name "{blob file name}.{signature format}.sig". Usage: - notation blob sign [flags] + notation blob sign [flags] Flags: -d, --debug debug mode @@ -194,22 +194,22 @@ An example for a successful signing: ```console $ notation blob sign /tmp/my-blob.bin -Successfully signed "/tmp/my-blob.bin" -Signature file written to "/absolute/path/to/cwd/my-blob.bin.jws.sig" +Successfully signed /tmp/my-blob.bin +Signature file written to /absolute/path/to/cwd/my-blob.bin.jws.sig ``` ### Sign a blob by generating the signature in a particular directory ```console $ notation blob sign --signature-directory /tmp/xyz/sigs /tmp/my-blob.bin -Successfully signed "/tmp/my-blob.bin" -Signature file written to "/tmp/xyz/sigs/my-blob.bin.jws.sig" +Successfully signed /tmp/my-blob.bin +Signature file written to /tmp/xyz/sigs/my-blob.bin.jws.sig ``` ### Sign a blob using a relative path ```console $ notation blob sign ./relative/path/my-blob.bin -Successfully signed "./relative/path/my-blob.bin" -Signature file written to "/absolute/path/to/cwd/my-blob.bin.jws.sig" +Successfully signed ./relative/path/my-blob.bin +Signature file written to /absolute/path/to/cwd/my-blob.bin.jws.sig ``` ### Sign a blob with a plugin @@ -226,8 +226,8 @@ notation blob sign --plugin --id /tmp/my-blob.bin # Use option "--signature-format" to set the signature format to COSE. $ notation blob sign --signature-format cose /tmp/my-blob.bin -Successfully signed "/tmp/my-blob.bin" -Signature file written to "/absolute/path/to/cwd/my-blob.bin.cose.sig" +Successfully signed /tmp/my-blob.bin +Signature file written to /absolute/path/to/cwd/my-blob.bin.cose.sig ``` ### Sign a blob using the default signing key diff --git a/test/e2e/suite/command/blob/sign.go b/test/e2e/suite/command/blob/sign.go index f9325c955..78f773fdd 100644 --- a/test/e2e/suite/command/blob/sign.go +++ b/test/e2e/suite/command/blob/sign.go @@ -30,7 +30,7 @@ var _ = Describe("notation blob sign", func() { // Success cases It("with blob sign", func() { HostWithBlob(BaseOptions(), func(notation *utils.ExecOpts, blobPath string, vhost *utils.VirtualHost) { - notation.Exec("blob", "sign", blobPath). + notation.Exec("blob", "sign", "--force", blobPath). MatchKeyWords(SignSuccessfully). MatchKeyWords("Signature file written to") }) @@ -38,7 +38,7 @@ var _ = Describe("notation blob sign", func() { It("with COSE format", func() { HostWithBlob(BaseOptions(), func(notation *utils.ExecOpts, blobPath string, vhost *utils.VirtualHost) { - notation.Exec("blob", "sign", "--signature-format", "cose", blobPath). + notation.Exec("blob", "sign", "--signature-format", "cose", "--force", blobPath). MatchKeyWords(SignSuccessfully). MatchKeyWords("Signature file written to") }) @@ -75,21 +75,12 @@ var _ = Describe("notation blob sign", func() { It("with signature directory", func() { HostWithBlob(BaseOptions(), func(notation *utils.ExecOpts, blobPath string, vhost *utils.VirtualHost) { blobDir := filepath.Dir(blobPath) - notation.Exec("blob", "sign", "--signature-directory", blobDir, blobPath). + notation.Exec("blob", "sign", "--force", "--signature-directory", blobDir, blobPath). MatchKeyWords(SignSuccessfully). MatchKeyWords(fmt.Sprintf("Signature file written to %q", filepath.Join(blobDir, "blobFile.jws.sig"))) }) }) - It("with force saving signature", func() { - HostWithBlob(BaseOptions(), func(notation *utils.ExecOpts, blobPath string, vhost *utils.VirtualHost) { - notation.Exec("blob", "sign", "--force", blobPath). - MatchErrKeyWords("Warning: existing signature file will be overwritten"). - MatchKeyWords(SignSuccessfully). - MatchKeyWords("Signature file written to") - }) - }) - It("with user metadata", func() { HostWithBlob(BaseOptions(), func(notation *utils.ExecOpts, blobPath string, vhost *utils.VirtualHost) { notation.Exec("blob", "sign", "--force", "--user-metadata", "k1=v1", "--user-metadata", "k2=v2", blobPath). From 752afe61c721df13bf247458685009de392da542 Mon Sep 17 00:00:00 2001 From: Patrick Zheng Date: Tue, 31 Dec 2024 11:25:45 +0800 Subject: [PATCH 17/53] fix e2e test Signed-off-by: Patrick Zheng --- test/e2e/suite/command/blob/sign.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/e2e/suite/command/blob/sign.go b/test/e2e/suite/command/blob/sign.go index 78f773fdd..867219b58 100644 --- a/test/e2e/suite/command/blob/sign.go +++ b/test/e2e/suite/command/blob/sign.go @@ -77,7 +77,7 @@ var _ = Describe("notation blob sign", func() { blobDir := filepath.Dir(blobPath) notation.Exec("blob", "sign", "--force", "--signature-directory", blobDir, blobPath). MatchKeyWords(SignSuccessfully). - MatchKeyWords(fmt.Sprintf("Signature file written to %q", filepath.Join(blobDir, "blobFile.jws.sig"))) + MatchKeyWords(fmt.Sprintf("Signature file written to %s", filepath.Join(blobDir, "blobFile.jws.sig"))) }) }) From bc8f710ba6af06db78e7df6864728a9b604805ee Mon Sep 17 00:00:00 2001 From: Patrick Zheng Date: Mon, 6 Jan 2025 13:29:39 +0800 Subject: [PATCH 18/53] update Signed-off-by: Patrick Zheng --- cmd/notation/blob/sign.go | 8 ++++---- specs/commandline/blob.md | 10 +++++----- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/cmd/notation/blob/sign.go b/cmd/notation/blob/sign.go index 023c00779..2ea700196 100644 --- a/cmd/notation/blob/sign.go +++ b/cmd/notation/blob/sign.go @@ -70,7 +70,7 @@ Example - Sign a blob artifact using the default signing key, with the default J notation blob sign Example - Sign a blob artifact by generating the signature in a particular directory: - notation blob sign --signature-directory + notation blob sign --signature-directory Example - Sign a blob artifact and skip user confirmations when overwriting existing signature: notation blob sign --force @@ -94,12 +94,12 @@ Example - Sign a blob artifact and specify the signature expiry duration, for ex notation blob sign --expiry 24h Example - Sign a blob artifact with timestamping: - notation blob sign --timestamp-url --timestamp-root-cert /@ + notation blob sign --timestamp-url --timestamp-root-cert ` command := &cobra.Command{ - Use: "sign [flags] ", - Short: "Sign blob artifacts", + Use: "sign [flags] ", + Short: "Produce a detached signature for a given blob", Long: longMessage, Args: func(cmd *cobra.Command, args []string) error { if len(args) == 0 { diff --git a/specs/commandline/blob.md b/specs/commandline/blob.md index 230b390ba..532d633f7 100644 --- a/specs/commandline/blob.md +++ b/specs/commandline/blob.md @@ -58,10 +58,10 @@ Usage: notation blob [command] Available Commands: - inspect inspect a signature associated with a blob - policy manage trust policy configuration for signed blobs - sign produce a detached signature for a given blob - verify verify a signature associated with a blob + inspect Inspect a signature associated with a blob + policy Manage trust policy configuration for signed blobs + sign Produce a detached signature for a given blob + verify Verify a signature associated with a blob Flags: -h, --help help for blob @@ -72,7 +72,7 @@ Flags: ```text Produce a detached signature for a given blob. -The signature file will be written to the currently working directory with file name `{blob file name}.{signature format}.sig`. +The signature file will be written to the currently working directory with file name "{blob file name}.{signature format}.sig". Usage: notation blob sign [flags] From 9ce5ac9cf741bacdbf6a0ba9a3c425fabd6f2639 Mon Sep 17 00:00:00 2001 From: Patrick Zheng Date: Mon, 6 Jan 2025 15:15:30 +0800 Subject: [PATCH 19/53] initial commit Signed-off-by: Patrick Zheng --- cmd/notation/blob/cmd.go | 1 + cmd/notation/blob/verify.go | 134 +++++++++++++++++++++++++++++++ cmd/notation/blob/verify_test.go | 75 +++++++++++++++++ cmd/notation/verify.go | 35 +------- cmd/notation/verify_test.go | 54 ------------- internal/cmd/verifier.go | 70 ++++++++++++++++ internal/cmd/verify_test.go | 72 +++++++++++++++++ specs/commandline/blob.md | 7 +- 8 files changed, 357 insertions(+), 91 deletions(-) create mode 100644 cmd/notation/blob/verify.go create mode 100644 cmd/notation/blob/verify_test.go create mode 100644 internal/cmd/verifier.go create mode 100644 internal/cmd/verify_test.go diff --git a/cmd/notation/blob/cmd.go b/cmd/notation/blob/cmd.go index 2883acabf..0ecb35c3b 100644 --- a/cmd/notation/blob/cmd.go +++ b/cmd/notation/blob/cmd.go @@ -24,6 +24,7 @@ func Cmd() *cobra.Command { command.AddCommand( signCommand(nil), + verifyCommand(nil), ) return command diff --git a/cmd/notation/blob/verify.go b/cmd/notation/blob/verify.go new file mode 100644 index 000000000..59206515a --- /dev/null +++ b/cmd/notation/blob/verify.go @@ -0,0 +1,134 @@ +// 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 blob + +import ( + "errors" + "path/filepath" + "strings" + + "github.com/notaryproject/notation-go" + "github.com/notaryproject/notation/internal/cmd" + "github.com/spf13/cobra" +) + +type blobVerifyOpts struct { + cmd.LoggingFlagOpts + signaturePath string + blobPath string + pluginConfig []string + userMetadata []string + policyStatementName string + blobMediaType string +} + +func verifyCommand(opts *blobVerifyOpts) *cobra.Command { + if opts == nil { + opts = &blobVerifyOpts{} + } + longMessage := `Verify a signature associated with a blob. + +Prerequisite: added a certificate into trust store and created a trust policy. + +Example - Verify a signature on a blob artifact: + notation blob verify --signature + +Example - Verify the signature on a blob artifact with user metadata: + notation blob verify --user-metadata --signature + +Example - Verify the signature on a blob artifact with media type: + notation blob verify --media-type --signature + +Example - Verify the signature on a blob artifact using a policy statement name: + notation blob verify --policy-name --signature +` + command := &cobra.Command{ + Use: "verify [flags] --signature ", + Short: "verify a signature associated with a blob", + Long: longMessage, + Args: func(cmd *cobra.Command, args []string) error { + if len(args) == 0 { + return errors.New("missing path to the blob artifact: use `notation blob verify --help` to see what parameters are required") + } + opts.blobPath = args[0] + return nil + }, + PreRunE: func(cmd *cobra.Command, args []string) error { + if opts.signaturePath == "" { + return errors.New("filepath of the signature cannot be empty") + } + if cmd.Flags().Changed("media-type") && opts.blobMediaType == "" { + return errors.New("--media-type is set but with empty value") + } + return nil + }, + RunE: func(cmd *cobra.Command, args []string) error { + return runVerify(cmd, opts) + }, + } + command.Flags().StringVar(&opts.signaturePath, "signature", "", "filepath of the signature to be verified") + command.Flags().StringArrayVar(&opts.pluginConfig, "plugin-config", nil, "{key}={value} pairs that are passed as it is to a plugin, if the verification is associated with a verification plugin, refer plugin documentation to set appropriate values") + command.Flags().StringVar(&opts.blobMediaType, "media-type", "", "media type of the blob to verify") + command.Flags().StringVar(&opts.policyStatementName, "policy-name", "", "policy name to verify against. If not provided, the global policy is used if exists") + cmd.SetPflagUserMetadata(command.Flags(), &opts.userMetadata, cmd.PflagUserMetadataVerifyUsage) + command.MarkFlagRequired("signature") + return command +} + +func runVerify(command *cobra.Command, opts *blobVerifyOpts) error { + // set log level + ctx := opts.LoggingFlagOpts.InitializeLogger(command.Context()) + + // initialize + blobVerifier, err := cmd.GetVerifier(ctx, true) + if err != nil { + return err + } + + // set up verification plugin config + pluginConfigs, err := cmd.ParseFlagMap(opts.pluginConfig, cmd.PflagPluginConfig.Name) + if err != nil { + return err + } + + // set up user metadata + userMetadata, err := cmd.ParseFlagMap(opts.userMetadata, cmd.PflagUserMetadata.Name) + if err != nil { + return err + } + + verifyBlobOpts := notation.VerifyBlobOptions{ + BlobVerifierVerifyOptions: notation.BlobVerifierVerifyOptions{ + SignatureMediaType: signatureFormat(opts.signaturePath), + PluginConfig: pluginConfigs, + UserMetadata: userMetadata, + TrustPolicyName: opts.policyStatementName, + }, + ContentMediaType: opts.blobMediaType, + } + _, outcomes, err := notation.VerifyBlob(ctx, blobVerifier, verifyBlobOpts) + // printOut := "placeholder" + // err = sharedutils.CheckVerificationFailure(outcomes, printOut, err) + // if err != nil { + // return err + // } + // sharedutils.ReportVerificationSuccess(outcomes, printOut) + return nil +} + +// signatureFormat returns the format of the signature file +func signatureFormat(signaturePath string) string { + signatureFileName := filepath.Base(signaturePath) + return strings.Split(signatureFileName, ".")[1] +} diff --git a/cmd/notation/blob/verify_test.go b/cmd/notation/blob/verify_test.go new file mode 100644 index 000000000..024d3c428 --- /dev/null +++ b/cmd/notation/blob/verify_test.go @@ -0,0 +1,75 @@ +// 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 blob + +import ( + "reflect" + "testing" +) + +func TestVerifyCommand_BasicArgs(t *testing.T) { + opts := &blobVerifyOpts{} + command := verifyCommand(opts) + expected := &blobVerifyOpts{ + signaturePath: "path", + blobPath: "path", + pluginConfig: []string{"key1=val1"}, + } + if err := command.ParseFlags([]string{ + expected.signaturePath, + expected.blobPath, + "--plugin-config", "key1=val1"}); err != nil { + t.Fatalf("Parse Flag failed: %v", err) + } + if err := command.Args(command, command.Flags().Args()); err != nil { + t.Fatalf("Parse args failed: %v", err) + } + if !reflect.DeepEqual(*expected, *opts) { + t.Fatalf("Expect blob verify opts: %v, got: %v", expected, opts) + } +} + +func TestVerifyCommand_MoreArgs(t *testing.T) { + opts := &blobVerifyOpts{} + command := verifyCommand(opts) + expected := &blobVerifyOpts{ + signaturePath: "path", + blobPath: "path", + pluginConfig: []string{"key1=val1", "key2=val2"}, + } + if err := command.ParseFlags([]string{ + expected.signaturePath, + expected.blobPath, + "--plugin-config", "key1=val1", + "--plugin-config", "key2=val2", + }); err != nil { + t.Fatalf("Parse Flag failed: %v", err) + } + if err := command.Args(command, command.Flags().Args()); err != nil { + t.Fatalf("Parse args failed: %v", err) + } + if !reflect.DeepEqual(*expected, *opts) { + t.Fatalf("Expect verify opts: %v, got: %v", expected, opts) + } +} + +func TestVerifyCommand_MissingArgs(t *testing.T) { + cmd := verifyCommand(nil) + if err := cmd.ParseFlags(nil); err != nil { + t.Fatalf("Parse Flag failed: %v", err) + } + if err := cmd.Args(cmd, cmd.Flags().Args()); err == nil { + t.Fatal("Parse Args expected error, but ok") + } +} diff --git a/cmd/notation/verify.go b/cmd/notation/verify.go index bfcb36d6f..b46ad302f 100644 --- a/cmd/notation/verify.go +++ b/cmd/notation/verify.go @@ -14,26 +14,19 @@ package main import ( - "context" "errors" "fmt" "io/fs" "os" "reflect" - "github.com/notaryproject/notation-core-go/revocation/purpose" "github.com/notaryproject/notation-go" - "github.com/notaryproject/notation-go/dir" - "github.com/notaryproject/notation-go/plugin" - "github.com/notaryproject/notation-go/verifier" "github.com/notaryproject/notation-go/verifier/trustpolicy" "github.com/notaryproject/notation-go/verifier/truststore" "github.com/notaryproject/notation/cmd/notation/internal/experimental" "github.com/notaryproject/notation/internal/cmd" "github.com/notaryproject/notation/internal/ioutil" "github.com/spf13/cobra" - - clirev "github.com/notaryproject/notation/internal/revocation" ) type verifyOpts struct { @@ -117,12 +110,12 @@ func runVerify(command *cobra.Command, opts *verifyOpts) error { ctx := opts.LoggingFlagOpts.InitializeLogger(command.Context()) // initialize - sigVerifier, err := getVerifier(ctx) + sigVerifier, err := cmd.GetVerifier(ctx, false) if err != nil { return err } - // set up verification plugin config. + // set up verification plugin config configs, err := cmd.ParseFlagMap(opts.pluginConfig, cmd.PflagPluginConfig.Name) if err != nil { return err @@ -225,27 +218,3 @@ func printMetadataIfPresent(outcome *notation.VerificationOutcome) { ioutil.PrintMetadataMap(os.Stdout, metadata) } } - -func getVerifier(ctx context.Context) (notation.Verifier, error) { - // revocation check - revocationCodeSigningValidator, err := clirev.NewRevocationValidator(ctx, purpose.CodeSigning) - if err != nil { - return nil, err - } - revocationTimestampingValidator, err := clirev.NewRevocationValidator(ctx, purpose.Timestamping) - if err != nil { - return nil, err - } - - // trust policy and trust store - policyDocument, err := trustpolicy.LoadOCIDocument() - if err != nil { - return nil, err - } - x509TrustStore := truststore.NewX509TrustStore(dir.ConfigFS()) - - return verifier.NewVerifierWithOptions(policyDocument, nil, x509TrustStore, plugin.NewCLIManager(dir.PluginFS()), verifier.VerifierOptions{ - RevocationCodeSigningValidator: revocationCodeSigningValidator, - RevocationTimestampingValidator: revocationTimestampingValidator, - }) -} diff --git a/cmd/notation/verify_test.go b/cmd/notation/verify_test.go index b68796436..6ae49b8c8 100644 --- a/cmd/notation/verify_test.go +++ b/cmd/notation/verify_test.go @@ -14,15 +14,8 @@ package main import ( - "context" - "encoding/json" - "os" - "path/filepath" "reflect" "testing" - - "github.com/notaryproject/notation-go/dir" - "github.com/notaryproject/notation-go/verifier/trustpolicy" ) func TestVerifyCommand_BasicArgs(t *testing.T) { @@ -87,50 +80,3 @@ func TestVerifyCommand_MissingArgs(t *testing.T) { t.Fatal("Parse Args expected error, but ok") } } - -func TestGetVerifier(t *testing.T) { - defer func(oldConfiDir, oldCacheDir string) { - dir.UserConfigDir = oldConfiDir - dir.UserCacheDir = oldCacheDir - }(dir.UserConfigDir, dir.UserCacheDir) - - t.Run("success", func(t *testing.T) { - tempRoot := t.TempDir() - dir.UserConfigDir = tempRoot - path := filepath.Join(tempRoot, "trustpolicy.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 := getVerifier(context.Background()) - if err != nil { - t.Fatal(err) - } - }) - - t.Run("non-existing trust policy", func(t *testing.T) { - dir.UserConfigDir = "/" - expectedErrMsg := "trust policy is not present. To create a trust policy, see: https://notaryproject.dev/docs/quickstart/#create-a-trust-policy" - _, err := getVerifier(context.Background()) - if err == nil || err.Error() != expectedErrMsg { - t.Fatalf("expected %s, but got %s", expectedErrMsg, err) - } - }) -} - -func dummyOCIPolicyDocument() trustpolicy.OCIDocument { - return trustpolicy.OCIDocument{ - Version: "1.0", - TrustPolicies: []trustpolicy.OCITrustPolicy{ - { - Name: "test-statement-name", - RegistryScopes: []string{"registry.acme-rockets.io/software/net-monitor"}, - SignatureVerification: trustpolicy.SignatureVerification{VerificationLevel: "strict"}, - TrustStores: []string{"ca:valid-trust-store", "signingAuthority:valid-trust-store"}, - TrustedIdentities: []string{"x509.subject:CN=Notation Test Root,O=Notary,L=Seattle,ST=WA,C=US"}, - }, - }, - } -} diff --git a/internal/cmd/verifier.go b/internal/cmd/verifier.go new file mode 100644 index 000000000..9ff15cde2 --- /dev/null +++ b/internal/cmd/verifier.go @@ -0,0 +1,70 @@ +// Copyright The Notary Project Authors. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package cmd + +import ( + "context" + + "github.com/notaryproject/notation-core-go/revocation/purpose" + "github.com/notaryproject/notation-go" + "github.com/notaryproject/notation-go/dir" + "github.com/notaryproject/notation-go/plugin" + "github.com/notaryproject/notation-go/verifier" + "github.com/notaryproject/notation-go/verifier/trustpolicy" + "github.com/notaryproject/notation-go/verifier/truststore" + + clirev "github.com/notaryproject/notation/internal/revocation" +) + +// Verifier is embedded with notation.BlobVerifier and notation.Verifier. +type Verifier interface { + notation.BlobVerifier + notation.Verifier +} + +// GetVerifier returns a Verifier. +// isBlob is set to true when verifying an arbitrary blob. +func GetVerifier(ctx context.Context, isBlob bool) (Verifier, error) { + // revocation check + revocationCodeSigningValidator, err := clirev.NewRevocationValidator(ctx, purpose.CodeSigning) + if err != nil { + return nil, err + } + revocationTimestampingValidator, err := clirev.NewRevocationValidator(ctx, purpose.Timestamping) + if err != nil { + return nil, err + } + + // trust policy and trust store + x509TrustStore := truststore.NewX509TrustStore(dir.ConfigFS()) + if isBlob { + blobPolicyDocument, err := trustpolicy.LoadBlobDocument() + if err != nil { + return nil, err + } + return verifier.NewVerifierWithOptions(nil, blobPolicyDocument, x509TrustStore, plugin.NewCLIManager(dir.PluginFS()), verifier.VerifierOptions{ + RevocationCodeSigningValidator: revocationCodeSigningValidator, + RevocationTimestampingValidator: revocationTimestampingValidator, + }) + } + + policyDocument, err := trustpolicy.LoadOCIDocument() + if err != nil { + return nil, err + } + return verifier.NewVerifierWithOptions(policyDocument, nil, x509TrustStore, plugin.NewCLIManager(dir.PluginFS()), verifier.VerifierOptions{ + RevocationCodeSigningValidator: revocationCodeSigningValidator, + RevocationTimestampingValidator: revocationTimestampingValidator, + }) +} diff --git a/internal/cmd/verify_test.go b/internal/cmd/verify_test.go new file mode 100644 index 000000000..7989d581c --- /dev/null +++ b/internal/cmd/verify_test.go @@ -0,0 +1,72 @@ +// Copyright The Notary Project Authors. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package cmd + +import ( + "context" + "encoding/json" + "os" + "path/filepath" + "testing" + + "github.com/notaryproject/notation-go/dir" + "github.com/notaryproject/notation-go/verifier/trustpolicy" +) + +func TestGetVerifier(t *testing.T) { + defer func(oldConfiDir, oldCacheDir string) { + dir.UserConfigDir = oldConfiDir + dir.UserCacheDir = oldCacheDir + }(dir.UserConfigDir, dir.UserCacheDir) + + t.Run("success", func(t *testing.T) { + tempRoot := t.TempDir() + dir.UserConfigDir = tempRoot + path := filepath.Join(tempRoot, "trustpolicy.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 := GetVerifier(context.Background(), false) + if err != nil { + t.Fatal(err) + } + }) + + t.Run("non-existing trust policy", func(t *testing.T) { + dir.UserConfigDir = "/" + expectedErrMsg := "trust policy is not present. To create a trust policy, see: https://notaryproject.dev/docs/quickstart/#create-a-trust-policy" + _, err := GetVerifier(context.Background(), false) + if err == nil || err.Error() != expectedErrMsg { + t.Fatalf("expected %s, but got %s", expectedErrMsg, err) + } + }) +} + +func dummyOCIPolicyDocument() trustpolicy.OCIDocument { + return trustpolicy.OCIDocument{ + Version: "1.0", + TrustPolicies: []trustpolicy.OCITrustPolicy{ + { + Name: "test-statement-name", + RegistryScopes: []string{"registry.acme-rockets.io/software/net-monitor"}, + SignatureVerification: trustpolicy.SignatureVerification{VerificationLevel: "strict"}, + TrustStores: []string{"ca:valid-trust-store", "signingAuthority:valid-trust-store"}, + TrustedIdentities: []string{"x509.subject:CN=Notation Test Root,O=Notary,L=Seattle,ST=WA,C=US"}, + }, + }, + } +} diff --git a/specs/commandline/blob.md b/specs/commandline/blob.md index 532d633f7..c9f9e0810 100644 --- a/specs/commandline/blob.md +++ b/specs/commandline/blob.md @@ -160,12 +160,11 @@ Usage: notation blob verify [flags] --signature Flags: - --signature string location of the blob signature file + --signature string filepath of the signature to be verified --media-type string optional media type of the blob to verify - --policy-name string optional policy name to verify against. If not provided, notation verifies against the global policy if it exists. + --policy-name string optional policy name to verify against. If not provided, the global policy is used if exists -m, --user-metadata stringArray user defined {key}={value} pairs that must be present in the signature for successful verification if provided --plugin-config stringArray {key}={value} pairs that are passed as it is to a plugin, if the verification is associated with a verification plugin, refer plugin documentation to set appropriate values - -o, --output string output format, options: 'json', 'text' (default "text") -d, --debug debug mode -v, --verbose verbose mode -h, --help help for inspect @@ -379,7 +378,7 @@ The steps to update blob trust policy configuration: ``` ## Verify blob signatures -The `notation blob verify` command can be used to verify blob signatures. In order to verify signatures, user will need to setup a trust policy file `trustpolicy.blob.json` with Policies for blobs. Below are two examples of how a policy configuration file can be setup for verifying blob signatures. +The `notation blob verify` command can be used to verify blob signatures. In order to verify signatures, user will need to setup a trust policy file `trustpolicy.blob.json` with policies for blobs. Below are two examples of how a policy configuration file can be setup for verifying blob signatures. - The Policy named "wabbit-networks-policy" is for verifying blob artifacts signed by Wabbit Networks. - Policy named "global-verification-policy" is for auditing verification results when user doesn't not provide `--policy-name` argument in `notation blob verify` command. From 9e84a3f8fd05bc0c92fa7cf2181f431799d1e23d Mon Sep 17 00:00:00 2001 From: Patrick Zheng Date: Tue, 7 Jan 2025 13:06:27 +0800 Subject: [PATCH 20/53] update Signed-off-by: Patrick Zheng --- cmd/notation/blob/verify.go | 42 +++++++++++++------- cmd/notation/verify.go | 71 +--------------------------------- internal/ioutil/print.go | 76 +++++++++++++++++++++++++++++++++++++ 3 files changed, 106 insertions(+), 83 deletions(-) diff --git a/cmd/notation/blob/verify.go b/cmd/notation/blob/verify.go index 59206515a..bbe9852af 100644 --- a/cmd/notation/blob/verify.go +++ b/cmd/notation/blob/verify.go @@ -15,11 +15,13 @@ package blob import ( "errors" + "os" "path/filepath" "strings" "github.com/notaryproject/notation-go" "github.com/notaryproject/notation/internal/cmd" + "github.com/notaryproject/notation/internal/ioutil" "github.com/spf13/cobra" ) @@ -86,44 +88,56 @@ Example - Verify the signature on a blob artifact using a policy statement name: return command } -func runVerify(command *cobra.Command, opts *blobVerifyOpts) error { +func runVerify(command *cobra.Command, cmdOpts *blobVerifyOpts) error { // set log level - ctx := opts.LoggingFlagOpts.InitializeLogger(command.Context()) + ctx := cmdOpts.LoggingFlagOpts.InitializeLogger(command.Context()) // initialize + blobFile, err := os.Open(cmdOpts.blobPath) + if err != nil { + return err + } + defer blobFile.Close() + + signatureBytes, err := os.ReadFile(cmdOpts.signaturePath) + if err != nil { + return err + } + blobVerifier, err := cmd.GetVerifier(ctx, true) if err != nil { return err } // set up verification plugin config - pluginConfigs, err := cmd.ParseFlagMap(opts.pluginConfig, cmd.PflagPluginConfig.Name) + pluginConfigs, err := cmd.ParseFlagMap(cmdOpts.pluginConfig, cmd.PflagPluginConfig.Name) if err != nil { return err } // set up user metadata - userMetadata, err := cmd.ParseFlagMap(opts.userMetadata, cmd.PflagUserMetadata.Name) + userMetadata, err := cmd.ParseFlagMap(cmdOpts.userMetadata, cmd.PflagUserMetadata.Name) if err != nil { return err } verifyBlobOpts := notation.VerifyBlobOptions{ BlobVerifierVerifyOptions: notation.BlobVerifierVerifyOptions{ - SignatureMediaType: signatureFormat(opts.signaturePath), + SignatureMediaType: signatureFormat(cmdOpts.signaturePath), PluginConfig: pluginConfigs, UserMetadata: userMetadata, - TrustPolicyName: opts.policyStatementName, + TrustPolicyName: cmdOpts.policyStatementName, }, - ContentMediaType: opts.blobMediaType, + ContentMediaType: cmdOpts.blobMediaType, + } + + _, outcome, err := notation.VerifyBlob(ctx, blobVerifier, blobFile, signatureBytes, verifyBlobOpts) + outcomes := []*notation.VerificationOutcome{outcome} + err = ioutil.PrintVerificationFailure(outcomes, cmdOpts.blobPath, err, true) + if err != nil { + return err } - _, outcomes, err := notation.VerifyBlob(ctx, blobVerifier, verifyBlobOpts) - // printOut := "placeholder" - // err = sharedutils.CheckVerificationFailure(outcomes, printOut, err) - // if err != nil { - // return err - // } - // sharedutils.ReportVerificationSuccess(outcomes, printOut) + ioutil.PrintVerificationSuccess(outcomes, cmdOpts.blobPath) return nil } diff --git a/cmd/notation/verify.go b/cmd/notation/verify.go index b46ad302f..8cea3dceb 100644 --- a/cmd/notation/verify.go +++ b/cmd/notation/verify.go @@ -16,13 +16,9 @@ package main import ( "errors" "fmt" - "io/fs" "os" - "reflect" "github.com/notaryproject/notation-go" - "github.com/notaryproject/notation-go/verifier/trustpolicy" - "github.com/notaryproject/notation-go/verifier/truststore" "github.com/notaryproject/notation/cmd/notation/internal/experimental" "github.com/notaryproject/notation/internal/cmd" "github.com/notaryproject/notation/internal/ioutil" @@ -148,73 +144,10 @@ func runVerify(command *cobra.Command, opts *verifyOpts) error { UserMetadata: userMetadata, } _, outcomes, err := notation.Verify(ctx, sigVerifier, sigRepo, verifyOpts) - err = checkVerificationFailure(outcomes, resolvedRef, err) + err = ioutil.PrintVerificationFailure(outcomes, resolvedRef, err, false) if err != nil { return err } - reportVerificationSuccess(outcomes, resolvedRef) + ioutil.PrintVerificationSuccess(outcomes, resolvedRef) return nil } - -func checkVerificationFailure(outcomes []*notation.VerificationOutcome, printOut string, err error) error { - // write out on failure - if err != nil || len(outcomes) == 0 { - if err != nil { - var errTrustStore truststore.TrustStoreError - if errors.As(err, &errTrustStore) { - if errors.Is(err, fs.ErrNotExist) { - return fmt.Errorf("%w. Use command 'notation cert add' to create and add trusted certificates to the trust store", errTrustStore) - } else { - return fmt.Errorf("%w. %w", errTrustStore, errTrustStore.InnerError) - } - } - - var errCertificate truststore.CertificateError - if errors.As(err, &errCertificate) { - if errors.Is(err, fs.ErrNotExist) { - return fmt.Errorf("%w. Use command 'notation cert add' to create and add trusted certificates to the trust store", errCertificate) - } else { - return fmt.Errorf("%w. %w", errCertificate, errCertificate.InnerError) - } - } - - var errorVerificationFailed notation.ErrorVerificationFailed - if !errors.As(err, &errorVerificationFailed) { - return fmt.Errorf("signature verification failed: %w", err) - } - } - return fmt.Errorf("signature verification failed for all the signatures associated with %s", printOut) - } - return nil -} - -func reportVerificationSuccess(outcomes []*notation.VerificationOutcome, printout string) { - // write out on success - outcome := outcomes[0] - // print out warning for any failed result with logged verification action - for _, result := range outcome.VerificationResults { - if result.Error != nil { - // at this point, the verification action has to be logged and - // it's failed - fmt.Fprintf(os.Stderr, "Warning: %v was set to %q and failed with error: %v\n", result.Type, result.Action, result.Error) - } - } - if reflect.DeepEqual(outcome.VerificationLevel, trustpolicy.LevelSkip) { - fmt.Println("Trust policy is configured to skip signature verification for", printout) - } else { - fmt.Println("Successfully verified signature for", printout) - printMetadataIfPresent(outcome) - } -} - -func printMetadataIfPresent(outcome *notation.VerificationOutcome) { - // the signature envelope is parsed as part of verification. - // since user metadata is only printed on successful verification, - // this error can be ignored - metadata, _ := outcome.UserMetadata() - - if len(metadata) > 0 { - fmt.Println("\nThe artifact was signed with the following user metadata.") - ioutil.PrintMetadataMap(os.Stdout, metadata) - } -} diff --git a/internal/ioutil/print.go b/internal/ioutil/print.go index a7a3bac6c..87718dfec 100644 --- a/internal/ioutil/print.go +++ b/internal/ioutil/print.go @@ -15,12 +15,19 @@ package ioutil import ( "encoding/json" + "errors" "fmt" "io" + "io/fs" + "os" "path/filepath" + "reflect" "text/tabwriter" + "github.com/notaryproject/notation-go" "github.com/notaryproject/notation-go/config" + "github.com/notaryproject/notation-go/verifier/trustpolicy" + "github.com/notaryproject/notation-go/verifier/truststore" ) func newTabWriter(w io.Writer) *tabwriter.Writer { @@ -91,3 +98,72 @@ func PrintObjectAsJSON(i interface{}) error { return nil } + +// PrintVerificationFailure prints out messages when verification fails +func PrintVerificationFailure(outcomes []*notation.VerificationOutcome, printOut string, err error, isBlob bool) error { + // write out on failure + if err != nil || len(outcomes) == 0 { + if err != nil { + var errTrustStore truststore.TrustStoreError + if errors.As(err, &errTrustStore) { + if errors.Is(err, fs.ErrNotExist) { + return fmt.Errorf("%w. Use command 'notation cert add' to create and add trusted certificates to the trust store", errTrustStore) + } else { + return fmt.Errorf("%w. %w", errTrustStore, errTrustStore.InnerError) + } + } + + var errCertificate truststore.CertificateError + if errors.As(err, &errCertificate) { + if errors.Is(err, fs.ErrNotExist) { + return fmt.Errorf("%w. Use command 'notation cert add' to create and add trusted certificates to the trust store", errCertificate) + } else { + return fmt.Errorf("%w. %w", errCertificate, errCertificate.InnerError) + } + } + + var errorVerificationFailed notation.ErrorVerificationFailed + if !errors.As(err, &errorVerificationFailed) { + return fmt.Errorf("signature verification failed: %w", err) + } + } + if isBlob { + return fmt.Errorf("provided signature verification failed against blob %s", printOut) + } + return fmt.Errorf("signature verification failed for all the signatures associated with %s", printOut) + } + return nil +} + +// PrintVerificationSuccess prints out messages when verification succeeds +func PrintVerificationSuccess(outcomes []*notation.VerificationOutcome, printout string) { + // write out on success + outcome := outcomes[0] + // print out warning for any failed result with logged verification action + for _, result := range outcome.VerificationResults { + if result.Error != nil { + // at this point, the verification action has to be logged and + // it's failed + fmt.Fprintf(os.Stderr, "Warning: %v was set to %q and failed with error: %v\n", result.Type, result.Action, result.Error) + } + } + if reflect.DeepEqual(outcome.VerificationLevel, trustpolicy.LevelSkip) { + fmt.Println("Trust policy is configured to skip signature verification for", printout) + } else { + fmt.Println("Successfully verified signature for", printout) + PrintMetadataIfPresent(outcome) + } +} + +// PrintMetadataIfPresent prints out user metadata if present +func PrintMetadataIfPresent(outcome *notation.VerificationOutcome) { + // the signature envelope is parsed as part of verification. + // since user metadata is only printed on successful verification, + // this error can be ignored + metadata, _ := outcome.UserMetadata() + + if len(metadata) > 0 { + fmt.Println("\nThe artifact was signed with the following user metadata.") + PrintMetadataMap(os.Stdout, metadata) + } +} From 2659ff763b83384b806a5e966847d1f8c21b408d Mon Sep 17 00:00:00 2001 From: Patrick Zheng Date: Tue, 7 Jan 2025 13:19:53 +0800 Subject: [PATCH 21/53] fix tests Signed-off-by: Patrick Zheng --- cmd/notation/blob/verify.go | 2 +- cmd/notation/blob/verify_test.go | 14 ++++++-------- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/cmd/notation/blob/verify.go b/cmd/notation/blob/verify.go index bbe9852af..7995376ce 100644 --- a/cmd/notation/blob/verify.go +++ b/cmd/notation/blob/verify.go @@ -27,8 +27,8 @@ import ( type blobVerifyOpts struct { cmd.LoggingFlagOpts - signaturePath string blobPath string + signaturePath string pluginConfig []string userMetadata []string policyStatementName string diff --git a/cmd/notation/blob/verify_test.go b/cmd/notation/blob/verify_test.go index 024d3c428..b3074ccfd 100644 --- a/cmd/notation/blob/verify_test.go +++ b/cmd/notation/blob/verify_test.go @@ -22,14 +22,12 @@ func TestVerifyCommand_BasicArgs(t *testing.T) { opts := &blobVerifyOpts{} command := verifyCommand(opts) expected := &blobVerifyOpts{ - signaturePath: "path", - blobPath: "path", - pluginConfig: []string{"key1=val1"}, + blobPath: "blob_path", + signaturePath: "sig_path", } if err := command.ParseFlags([]string{ - expected.signaturePath, expected.blobPath, - "--plugin-config", "key1=val1"}); err != nil { + "--signature", expected.signaturePath}); err != nil { t.Fatalf("Parse Flag failed: %v", err) } if err := command.Args(command, command.Flags().Args()); err != nil { @@ -44,13 +42,13 @@ func TestVerifyCommand_MoreArgs(t *testing.T) { opts := &blobVerifyOpts{} command := verifyCommand(opts) expected := &blobVerifyOpts{ - signaturePath: "path", - blobPath: "path", + blobPath: "blob_path", + signaturePath: "sig_path", pluginConfig: []string{"key1=val1", "key2=val2"}, } if err := command.ParseFlags([]string{ - expected.signaturePath, expected.blobPath, + "--signature", expected.signaturePath, "--plugin-config", "key1=val1", "--plugin-config", "key2=val2", }); err != nil { From a1d856265e7e75ec427fe8d587a6f9b2cb11bbf0 Mon Sep 17 00:00:00 2001 From: Patrick Zheng Date: Thu, 9 Jan 2025 16:21:09 +0800 Subject: [PATCH 22/53] update Signed-off-by: Patrick Zheng --- cmd/notation/blob/verify.go | 2 - go.mod | 10 +- go.sum | 46 +++-- internal/cmd/verifier_test.go | 162 ++++++++++++++++++ internal/cmd/verify_test.go | 72 -------- test/e2e/go.mod | 6 +- test/e2e/go.sum | 12 +- test/e2e/internal/notation/host.go | 34 +++- test/e2e/internal/notation/init.go | 18 +- test/e2e/plugin/go.mod | 14 +- test/e2e/plugin/go.sum | 56 ++++-- test/e2e/run.sh | 1 + test/e2e/suite/command/blob/verify.go | 45 +++++ test/e2e/suite/command/policy.go | 10 +- test/e2e/suite/command/sign.go | 2 +- test/e2e/suite/scenario/quickstart.go | 2 +- .../e2e/suite/trustpolicy/multi_statements.go | 8 +- test/e2e/suite/trustpolicy/registry_scope.go | 16 +- test/e2e/suite/trustpolicy/trust_store.go | 12 +- .../e2e/suite/trustpolicy/trusted_identity.go | 22 +-- .../suite/trustpolicy/verification_level.go | 46 ++--- .../blob/trustpolicies/trustpolicy.blob.json | 26 +++ 22 files changed, 421 insertions(+), 201 deletions(-) create mode 100644 internal/cmd/verifier_test.go delete mode 100644 internal/cmd/verify_test.go create mode 100644 test/e2e/suite/command/blob/verify.go create mode 100644 test/e2e/testdata/blob/trustpolicies/trustpolicy.blob.json diff --git a/cmd/notation/blob/verify.go b/cmd/notation/blob/verify.go index 7995376ce..ba56a36b6 100644 --- a/cmd/notation/blob/verify.go +++ b/cmd/notation/blob/verify.go @@ -103,7 +103,6 @@ func runVerify(command *cobra.Command, cmdOpts *blobVerifyOpts) error { if err != nil { return err } - blobVerifier, err := cmd.GetVerifier(ctx, true) if err != nil { return err @@ -130,7 +129,6 @@ func runVerify(command *cobra.Command, cmdOpts *blobVerifyOpts) error { }, ContentMediaType: cmdOpts.blobMediaType, } - _, outcome, err := notation.VerifyBlob(ctx, blobVerifier, blobFile, signatureBytes, verifyBlobOpts) outcomes := []*notation.VerificationOutcome{outcome} err = ioutil.PrintVerificationFailure(outcomes, cmdOpts.blobPath, err, true) diff --git a/go.mod b/go.mod index 042764960..888434646 100644 --- a/go.mod +++ b/go.mod @@ -3,8 +3,8 @@ module github.com/notaryproject/notation go 1.23 require ( - github.com/notaryproject/notation-core-go v1.2.0-rc.1.0.20241129024749-95d89543c9f9 - github.com/notaryproject/notation-go v1.2.0-beta.1.0.20241202020354-95bac0082974 + github.com/notaryproject/notation-core-go v1.2.0-rc.2 + github.com/notaryproject/notation-go v1.2.0-beta.1.0.20250107003620-26ce0894a624 github.com/notaryproject/tspclient-go v1.0.0-rc.1 github.com/opencontainers/go-digest v1.0.0 github.com/opencontainers/image-spec v1.1.0 @@ -18,8 +18,8 @@ require ( require ( github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 // indirect github.com/fxamacker/cbor/v2 v2.7.0 // indirect - github.com/go-asn1-ber/asn1-ber v1.5.5 // indirect - github.com/go-ldap/ldap/v3 v3.4.8 // indirect + github.com/go-asn1-ber/asn1-ber v1.5.7 // indirect + github.com/go-ldap/ldap/v3 v3.4.10 // indirect github.com/golang-jwt/jwt/v4 v4.5.1 // indirect github.com/google/uuid v1.6.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect @@ -28,6 +28,6 @@ require ( github.com/x448/float16 v0.8.4 // indirect golang.org/x/crypto v0.31.0 // indirect golang.org/x/mod v0.22.0 // indirect - golang.org/x/sync v0.6.0 // indirect + golang.org/x/sync v0.10.0 // indirect golang.org/x/sys v0.28.0 // indirect ) diff --git a/go.sum b/go.sum index 653841d30..34a5fddac 100644 --- a/go.sum +++ b/go.sum @@ -8,12 +8,13 @@ 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/go-asn1-ber/asn1-ber v1.5.7 h1:DTX+lbVTWaTw1hQ+PbZPlnDZPEIs0SS/GCZAl535dDk= +github.com/go-asn1-ber/asn1-ber v1.5.7/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0= +github.com/go-ldap/ldap/v3 v3.4.10 h1:ot/iwPOhfpNVgB1o+AVXljizWZ9JTp7YF5oeyONmcJU= +github.com/go-ldap/ldap/v3 v3.4.10/go.mod h1:JXh4Uxgi40P6E9rdsYqpUtbW46D9UTjJ9QSwGRznplY= github.com/golang-jwt/jwt/v4 v4.5.1 h1:JdqV9zKUdtaa9gdPlywC3aeoEsR681PlKC+4F5gQgeo= github.com/golang-jwt/jwt/v4 v4.5.1/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 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= @@ -35,10 +36,10 @@ 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.2.0-rc.1.0.20241129024749-95d89543c9f9 h1:FURo9xpGLKmghWCcWypCPQTlcOGKxzayeXacGfb8WUU= -github.com/notaryproject/notation-core-go v1.2.0-rc.1.0.20241129024749-95d89543c9f9/go.mod h1:Umjn4NKGmuHpVffMgKVcUnArNG3Qtd3duKYpPILUBg4= -github.com/notaryproject/notation-go v1.2.0-beta.1.0.20241202020354-95bac0082974 h1:EQ9DC25U7hWbBIOlwINxPhr9QEyixg1/Fo5ZZW+3JSU= -github.com/notaryproject/notation-go v1.2.0-beta.1.0.20241202020354-95bac0082974/go.mod h1:6a3/g7yD/8dxxBpimzUWthH8DLBrzHs4RTzdz9CALvw= +github.com/notaryproject/notation-core-go v1.2.0-rc.2 h1:0jOItalNwBNUhyuc5PPHQxO3jIZ5xRYq+IvRMQXNbuE= +github.com/notaryproject/notation-core-go v1.2.0-rc.2/go.mod h1:7aIcavfywFvBQoYyfVFJB501kt7Etqyubrt5mhJBG2c= +github.com/notaryproject/notation-go v1.2.0-beta.1.0.20250107003620-26ce0894a624 h1:JCJ+64H1A/aYhNaUak+1DV4dY2uL3L5GFMRLzrh9tDM= +github.com/notaryproject/notation-go v1.2.0-beta.1.0.20250107003620-26ce0894a624/go.mod h1:1QaHYG/UOeAYhfLBipsSxquu3BheRm7a+5RODcc5nQg= 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 v1.0.0-rc.1 h1:KcHxlqg6Adt4kzGLw012i0YMLlwGwToiR129c6IQ7Ys= @@ -73,12 +74,16 @@ github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5t 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.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= 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.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U= golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= 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.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4= golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -88,14 +93,19 @@ golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug 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.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= 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/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= +golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I= +golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= 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.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= +golang.org/x/sync v0.10.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= @@ -104,16 +114,19 @@ golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBc 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.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE= 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.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= -golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58= +golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= golang.org/x/term v0.27.0 h1:WP60Sv1nlK1T6SupCHbXzSaN0b9wUmsPoRS9b61A23Q= golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -121,11 +134,16 @@ 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.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= 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/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= +golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= 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= diff --git a/internal/cmd/verifier_test.go b/internal/cmd/verifier_test.go new file mode 100644 index 000000000..8a7e5bc82 --- /dev/null +++ b/internal/cmd/verifier_test.go @@ -0,0 +1,162 @@ +// Copyright The Notary Project Authors. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package cmd + +import ( + "context" + "encoding/json" + "os" + "path/filepath" + "testing" + + "github.com/notaryproject/notation-go/dir" + "github.com/notaryproject/notation-go/verifier/trustpolicy" +) + +func TestGetVerifier(t *testing.T) { + defer func(oldConfiDir, oldCacheDir string) { + dir.UserConfigDir = oldConfiDir + dir.UserCacheDir = oldCacheDir + }(dir.UserConfigDir, dir.UserCacheDir) + + t.Run("oci success", func(t *testing.T) { + tempRoot := t.TempDir() + dir.UserConfigDir = tempRoot + path := filepath.Join(tempRoot, "trustpolicy.oci.json") + policyJson, _ := json.Marshal(dummyOCIPolicyDocument(false)) + if err := os.WriteFile(path, policyJson, 0600); err != nil { + t.Fatalf("write oci policy file failed. Error: %v", err) + } + t.Cleanup(func() { os.RemoveAll(tempRoot) }) + + _, err := GetVerifier(context.Background(), false) + if err != nil { + t.Fatal(err) + } + }) + + t.Run("blob success", func(t *testing.T) { + tempRoot := t.TempDir() + dir.UserConfigDir = tempRoot + path := filepath.Join(tempRoot, "trustpolicy.blob.json") + policyJson, _ := json.Marshal(dummyBlobPolicyDocument(false)) + if err := os.WriteFile(path, policyJson, 0600); err != nil { + t.Fatalf("write blob policy file failed. Error: %v", err) + } + t.Cleanup(func() { os.RemoveAll(tempRoot) }) + + _, err := GetVerifier(context.Background(), true) + if err != nil { + t.Fatal(err) + } + }) + + t.Run("non-existing oci trust policy", func(t *testing.T) { + dir.UserConfigDir = "/" + expectedErrMsg := "trust policy is not present. To create a trust policy, see: https://notaryproject.dev/docs/quickstart/#create-a-trust-policy" + _, err := GetVerifier(context.Background(), false) + if err == nil || err.Error() != expectedErrMsg { + t.Fatalf("expected %s, but got %s", expectedErrMsg, err) + } + }) + + t.Run("non-existing blob trust policy", func(t *testing.T) { + dir.UserConfigDir = "/" + expectedErrMsg := "trust policy is not present. To create a trust policy, see: https://notaryproject.dev/docs/quickstart/#create-a-trust-policy" + _, err := GetVerifier(context.Background(), true) + if err == nil || err.Error() != expectedErrMsg { + t.Fatalf("expected %s, but got %s", expectedErrMsg, err) + } + }) + + t.Run("invalid oci trust policy", func(t *testing.T) { + tempRoot := t.TempDir() + dir.UserConfigDir = tempRoot + path := filepath.Join(tempRoot, "trustpolicy.oci.json") + policyJson, _ := json.Marshal(dummyOCIPolicyDocument(true)) + if err := os.WriteFile(path, policyJson, 0600); err != nil { + t.Fatalf("write oci policy file failed. Error: %v", err) + } + t.Cleanup(func() { os.RemoveAll(tempRoot) }) + + expectedErrMsg := "oci trust policy document has empty version, version must be specified" + _, err := GetVerifier(context.Background(), false) + if err == nil || err.Error() != expectedErrMsg { + t.Fatalf("expected %s, but got %s", expectedErrMsg, err) + } + }) + + t.Run("invalid blob trust policy", func(t *testing.T) { + tempRoot := t.TempDir() + dir.UserConfigDir = tempRoot + path := filepath.Join(tempRoot, "trustpolicy.blob.json") + policyJson, _ := json.Marshal(dummyBlobPolicyDocument(true)) + if err := os.WriteFile(path, policyJson, 0600); err != nil { + t.Fatalf("write blob policy file failed. Error: %v", err) + } + t.Cleanup(func() { os.RemoveAll(tempRoot) }) + + expectedErrMsg := "blob trust policy has empty version, version must be specified" + _, err := GetVerifier(context.Background(), true) + if err == nil || err.Error() != expectedErrMsg { + t.Fatalf("expected %s, but got %s", expectedErrMsg, err) + } + }) +} + +func dummyOCIPolicyDocument(invalid bool) trustpolicy.OCIDocument { + if invalid { + return trustpolicy.OCIDocument{ + Version: "", + } + } + return trustpolicy.OCIDocument{ + Version: "1.0", + TrustPolicies: []trustpolicy.OCITrustPolicy{ + { + Name: "test-oci-statement", + RegistryScopes: []string{"registry.acme-rockets.io/software/net-monitor"}, + SignatureVerification: trustpolicy.SignatureVerification{VerificationLevel: "strict"}, + TrustStores: []string{"ca:valid-trust-store", "signingAuthority:valid-trust-store"}, + TrustedIdentities: []string{"x509.subject:CN=Notation Test Root,O=Notation,L=Seattle,ST=WA,C=US"}, + }, + }, + } +} + +func dummyBlobPolicyDocument(invalid bool) trustpolicy.BlobDocument { + if invalid { + return trustpolicy.BlobDocument{ + Version: "", + } + } + return trustpolicy.BlobDocument{ + Version: "1.0", + TrustPolicies: []trustpolicy.BlobTrustPolicy{ + { + Name: "test-blob-statement", + SignatureVerification: trustpolicy.SignatureVerification{VerificationLevel: "strict"}, + TrustStores: []string{"ca:valid-trust-store", "signingAuthority:valid-trust-store"}, + TrustedIdentities: []string{"x509.subject:CN=Notation Test Root,O=Notation,L=Seattle,ST=WA,C=US"}, + }, + { + Name: "test-blob-statement-global", + SignatureVerification: trustpolicy.SignatureVerification{VerificationLevel: "strict"}, + TrustStores: []string{"ca:valid-trust-store", "signingAuthority:valid-trust-store"}, + TrustedIdentities: []string{"x509.subject:CN=Notation Test Root,O=Notation,L=Seattle,ST=WA,C=US"}, + GlobalPolicy: true, + }, + }, + } +} diff --git a/internal/cmd/verify_test.go b/internal/cmd/verify_test.go deleted file mode 100644 index 7989d581c..000000000 --- a/internal/cmd/verify_test.go +++ /dev/null @@ -1,72 +0,0 @@ -// Copyright The Notary Project Authors. -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package cmd - -import ( - "context" - "encoding/json" - "os" - "path/filepath" - "testing" - - "github.com/notaryproject/notation-go/dir" - "github.com/notaryproject/notation-go/verifier/trustpolicy" -) - -func TestGetVerifier(t *testing.T) { - defer func(oldConfiDir, oldCacheDir string) { - dir.UserConfigDir = oldConfiDir - dir.UserCacheDir = oldCacheDir - }(dir.UserConfigDir, dir.UserCacheDir) - - t.Run("success", func(t *testing.T) { - tempRoot := t.TempDir() - dir.UserConfigDir = tempRoot - path := filepath.Join(tempRoot, "trustpolicy.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 := GetVerifier(context.Background(), false) - if err != nil { - t.Fatal(err) - } - }) - - t.Run("non-existing trust policy", func(t *testing.T) { - dir.UserConfigDir = "/" - expectedErrMsg := "trust policy is not present. To create a trust policy, see: https://notaryproject.dev/docs/quickstart/#create-a-trust-policy" - _, err := GetVerifier(context.Background(), false) - if err == nil || err.Error() != expectedErrMsg { - t.Fatalf("expected %s, but got %s", expectedErrMsg, err) - } - }) -} - -func dummyOCIPolicyDocument() trustpolicy.OCIDocument { - return trustpolicy.OCIDocument{ - Version: "1.0", - TrustPolicies: []trustpolicy.OCITrustPolicy{ - { - Name: "test-statement-name", - RegistryScopes: []string{"registry.acme-rockets.io/software/net-monitor"}, - SignatureVerification: trustpolicy.SignatureVerification{VerificationLevel: "strict"}, - TrustStores: []string{"ca:valid-trust-store", "signingAuthority:valid-trust-store"}, - TrustedIdentities: []string{"x509.subject:CN=Notation Test Root,O=Notary,L=Seattle,ST=WA,C=US"}, - }, - }, - } -} diff --git a/test/e2e/go.mod b/test/e2e/go.mod index ad0fadc78..0829a1938 100644 --- a/test/e2e/go.mod +++ b/test/e2e/go.mod @@ -3,8 +3,8 @@ module github.com/notaryproject/notation/test/e2e go 1.23 require ( - github.com/notaryproject/notation-core-go v1.2.0-rc.1.0.20241129024749-95d89543c9f9 - github.com/notaryproject/notation-go v1.2.0-beta.1.0.20241202020354-95bac0082974 + github.com/notaryproject/notation-core-go v1.2.0-rc.2 + github.com/notaryproject/notation-go v1.2.0-beta.1.0.20250107003620-26ce0894a624 github.com/onsi/ginkgo/v2 v2.22.1 github.com/onsi/gomega v1.36.2 github.com/opencontainers/image-spec v1.1.0 @@ -17,7 +17,7 @@ require ( github.com/go-task/slim-sprig/v3 v3.0.0 // indirect github.com/google/go-cmp v0.6.0 // indirect github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad // indirect - github.com/notaryproject/tspclient-go v0.2.1-0.20241030015323-90a141e7525c // indirect + github.com/notaryproject/tspclient-go v1.0.0-rc.1 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect github.com/veraison/go-cose v1.3.0 // indirect github.com/x448/float16 v0.8.4 // indirect diff --git a/test/e2e/go.sum b/test/e2e/go.sum index a430a03ac..ce903a174 100644 --- a/test/e2e/go.sum +++ b/test/e2e/go.sum @@ -10,12 +10,12 @@ github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad h1:a6HEuzUHeKH6hwfN/ZoQgRgVIWFJljSWa/zetS2WTvg= github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144= -github.com/notaryproject/notation-core-go v1.2.0-rc.1.0.20241129024749-95d89543c9f9 h1:FURo9xpGLKmghWCcWypCPQTlcOGKxzayeXacGfb8WUU= -github.com/notaryproject/notation-core-go v1.2.0-rc.1.0.20241129024749-95d89543c9f9/go.mod h1:Umjn4NKGmuHpVffMgKVcUnArNG3Qtd3duKYpPILUBg4= -github.com/notaryproject/notation-go v1.2.0-beta.1.0.20241202020354-95bac0082974 h1:EQ9DC25U7hWbBIOlwINxPhr9QEyixg1/Fo5ZZW+3JSU= -github.com/notaryproject/notation-go v1.2.0-beta.1.0.20241202020354-95bac0082974/go.mod h1:6a3/g7yD/8dxxBpimzUWthH8DLBrzHs4RTzdz9CALvw= -github.com/notaryproject/tspclient-go v0.2.1-0.20241030015323-90a141e7525c h1:bX6gGxFw9+DShmYTgbD+vr6neF1SoXIMUU2fDgdLsfA= -github.com/notaryproject/tspclient-go v0.2.1-0.20241030015323-90a141e7525c/go.mod h1:LGyA/6Kwd2FlM0uk8Vc5il3j0CddbWSHBj/4kxQDbjs= +github.com/notaryproject/notation-core-go v1.2.0-rc.2 h1:0jOItalNwBNUhyuc5PPHQxO3jIZ5xRYq+IvRMQXNbuE= +github.com/notaryproject/notation-core-go v1.2.0-rc.2/go.mod h1:7aIcavfywFvBQoYyfVFJB501kt7Etqyubrt5mhJBG2c= +github.com/notaryproject/notation-go v1.2.0-beta.1.0.20250107003620-26ce0894a624 h1:JCJ+64H1A/aYhNaUak+1DV4dY2uL3L5GFMRLzrh9tDM= +github.com/notaryproject/notation-go v1.2.0-beta.1.0.20250107003620-26ce0894a624/go.mod h1:1QaHYG/UOeAYhfLBipsSxquu3BheRm7a+5RODcc5nQg= +github.com/notaryproject/tspclient-go v1.0.0-rc.1 h1:KcHxlqg6Adt4kzGLw012i0YMLlwGwToiR129c6IQ7Ys= +github.com/notaryproject/tspclient-go v1.0.0-rc.1/go.mod h1:LGyA/6Kwd2FlM0uk8Vc5il3j0CddbWSHBj/4kxQDbjs= github.com/onsi/ginkgo/v2 v2.22.1 h1:QW7tbJAUDyVDVOM5dFa7qaybo+CRfR7bemlQUN6Z8aM= github.com/onsi/ginkgo/v2 v2.22.1/go.mod h1:S6aTpoRsSq2cZOd+pssHAlKW/Q/jZt6cPrPlnj4a1xM= github.com/onsi/gomega v1.36.2 h1:koNYke6TVk6ZmnyHrCXba/T/MoLBXFjeC1PtvYgw0A8= diff --git a/test/e2e/internal/notation/host.go b/test/e2e/internal/notation/host.go index d362e1fe2..f91672bbb 100644 --- a/test/e2e/internal/notation/host.go +++ b/test/e2e/internal/notation/host.go @@ -140,14 +140,24 @@ func Opts(options ...utils.HostOption) []utils.HostOption { return options } -// BaseOptions returns a list of base Options for a valid notation. +// BaseOptions returns a list of base Options for a valid notation // testing environment. func BaseOptions() []utils.HostOption { return Opts( AuthOption("", ""), AddKeyOption(filepath.Join(NotationE2ELocalKeysDir, "e2e.key"), filepath.Join(NotationE2ELocalKeysDir, "e2e.crt")), AddTrustStoreOption("e2e", filepath.Join(NotationE2ELocalKeysDir, "e2e.crt")), - AddTrustPolicyOption("trustpolicy.json"), + AddTrustPolicyOption("trustpolicy.json", false), + ) +} + +// BaseBlobOptions returns a list of base blob options for a valid notation +// testing environment. +func BaseBlobOptions() []utils.HostOption { + return Opts( + AddKeyOption(filepath.Join(NotationE2ELocalKeysDir, "e2e.key"), filepath.Join(NotationE2ELocalKeysDir, "e2e.crt")), + AddTrustStoreOption("e2e", filepath.Join(NotationE2ELocalKeysDir, "e2e.crt")), + AddTrustPolicyOption("trustpolicy.blob.json", true), ) } @@ -156,9 +166,9 @@ func BaseOptions() []utils.HostOption { func TimestampOptions(verifyTimestamp string) []utils.HostOption { var trustPolicyOption utils.HostOption if verifyTimestamp == "afterCertExpiry" { - trustPolicyOption = AddTrustPolicyOption("timestamp_after_cert_expiry_trustpolicy.json") + trustPolicyOption = AddTrustPolicyOption("timestamp_after_cert_expiry_trustpolicy.json", false) } else { - trustPolicyOption = AddTrustPolicyOption("timestamp_trustpolicy.json") + trustPolicyOption = AddTrustPolicyOption("timestamp_trustpolicy.json", false) } return Opts( @@ -176,7 +186,7 @@ func CRLOptions() []utils.HostOption { AuthOption("", ""), AddKeyOption(filepath.Join(NotationE2EConfigPath, "crl", "leaf.key"), filepath.Join(NotationE2EConfigPath, "crl", "certchain_with_crl.pem")), AddTrustStoreOption("e2e", filepath.Join(NotationE2EConfigPath, "crl", "root.crt")), - AddTrustPolicyOption("trustpolicy.json"), + AddTrustPolicyOption("trustpolicy.json", false), ) } @@ -185,7 +195,7 @@ func BaseOptionsWithExperimental() []utils.HostOption { AuthOption("", ""), AddKeyOption(filepath.Join(NotationE2ELocalKeysDir, "e2e.key"), filepath.Join(NotationE2ELocalKeysDir, "e2e.crt")), AddTrustStoreOption("e2e", filepath.Join(NotationE2ELocalKeysDir, "e2e.crt")), - AddTrustPolicyOption("trustpolicy.json"), + AddTrustPolicyOption("trustpolicy.json", false), EnableExperimental(), ) } @@ -196,7 +206,7 @@ func TestLoginOptions() []utils.HostOption { return Opts( AddKeyOption(filepath.Join(NotationE2ELocalKeysDir, "e2e.key"), filepath.Join(NotationE2ELocalKeysDir, "e2e.crt")), AddTrustStoreOption("e2e", filepath.Join(NotationE2ELocalKeysDir, "e2e.crt")), - AddTrustPolicyOption("trustpolicy.json"), + AddTrustPolicyOption("trustpolicy.json", false), AddConfigJsonOption("pass_credential_helper_config.json"), ) } @@ -251,7 +261,15 @@ func AddTimestampTrustStoreOption(namedstore string, srcCertPath string) utils.H } // AddTrustPolicyOption adds a valid trust policy for testing. -func AddTrustPolicyOption(trustpolicyName string) utils.HostOption { +func AddTrustPolicyOption(trustpolicyName string, isBlob bool) utils.HostOption { + if isBlob { + return func(vhost *utils.VirtualHost) error { + return copyFile( + filepath.Join(BlobTrustPolicyPath, trustpolicyName), + vhost.AbsolutePath(NotationDirName, BlobTrustPolicyName), + ) + } + } return func(vhost *utils.VirtualHost) error { return copyFile( filepath.Join(NotationE2ETrustPolicyDir, trustpolicyName), diff --git a/test/e2e/internal/notation/init.go b/test/e2e/internal/notation/init.go index c082a6780..1bd92b9ac 100644 --- a/test/e2e/internal/notation/init.go +++ b/test/e2e/internal/notation/init.go @@ -23,13 +23,14 @@ import ( ) const ( - NotationDirName = "notation" - TrustPolicyName = "trustpolicy.json" - TrustStoreDirName = "truststore" - TrustStoreTypeCA = "ca" - PluginDirName = "plugins" - PluginName = "e2e-plugin" - ConfigJsonName = "config.json" + NotationDirName = "notation" + BlobTrustPolicyName = "trustpolicy.blob.json" + TrustPolicyName = "trustpolicy.json" + TrustStoreDirName = "truststore" + TrustStoreTypeCA = "ca" + PluginDirName = "plugins" + PluginName = "e2e-plugin" + ConfigJsonName = "config.json" ) const ( @@ -47,6 +48,7 @@ const ( envKeyTestRepo = "NOTATION_E2E_TEST_REPO" envKeyTestTag = "NOTATION_E2E_TEST_TAG" envKeyBlobPath = "NOTATION_E2E_BLOB_PATH" + envKeyNotationE2EBlobTrustPolicyPath = "NOTATION_E2E_BLOB_TRUST_POLICY_PATH" ) var ( @@ -71,6 +73,7 @@ var ( TestTag string RegistryStoragePath string BlobPath string + BlobTrustPolicyPath string ) func init() { @@ -87,6 +90,7 @@ func setUp() { setPathValue(envKeyOCILayoutPath, &OCILayoutPath) setPathValue(envKeyBlobPath, &BlobPath) + setPathValue(envKeyNotationE2EBlobTrustPolicyPath, &BlobTrustPolicyPath) setValue(envKeyTestRepo, &TestRepoUri) setValue(envKeyTestTag, &TestTag) } diff --git a/test/e2e/plugin/go.mod b/test/e2e/plugin/go.mod index 51bd4dd06..b48fc08cb 100644 --- a/test/e2e/plugin/go.mod +++ b/test/e2e/plugin/go.mod @@ -4,8 +4,8 @@ go 1.23 require ( github.com/golang-jwt/jwt v3.2.2+incompatible - github.com/notaryproject/notation-core-go v1.2.0-rc.1.0.20241129024749-95d89543c9f9 - github.com/notaryproject/notation-go v1.2.0-beta.1.0.20241202020354-95bac0082974 + github.com/notaryproject/notation-core-go v1.2.0-rc.2 + github.com/notaryproject/notation-go v1.2.0-beta.1.0.20250107003620-26ce0894a624 github.com/notaryproject/notation-plugin-framework-go v1.0.0 github.com/spf13/cobra v1.8.1 ) @@ -13,19 +13,19 @@ require ( require ( github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 // indirect github.com/fxamacker/cbor/v2 v2.7.0 // indirect - github.com/go-asn1-ber/asn1-ber v1.5.5 // indirect - github.com/go-ldap/ldap/v3 v3.4.8 // indirect + github.com/go-asn1-ber/asn1-ber v1.5.7 // indirect + github.com/go-ldap/ldap/v3 v3.4.10 // indirect github.com/golang-jwt/jwt/v4 v4.5.1 // indirect github.com/google/uuid v1.6.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect - github.com/notaryproject/tspclient-go v0.2.1-0.20241030015323-90a141e7525c // indirect + github.com/notaryproject/tspclient-go v1.0.0-rc.1 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.1.0 // indirect github.com/spf13/pflag v1.0.5 // indirect github.com/veraison/go-cose v1.3.0 // indirect github.com/x448/float16 v0.8.4 // indirect - golang.org/x/crypto v0.29.0 // indirect + golang.org/x/crypto v0.31.0 // indirect golang.org/x/mod v0.22.0 // indirect - golang.org/x/sync v0.6.0 // indirect + golang.org/x/sync v0.10.0 // indirect oras.land/oras-go/v2 v2.5.0 // indirect ) diff --git a/test/e2e/plugin/go.sum b/test/e2e/plugin/go.sum index 4dccc8be0..6c88aef63 100644 --- a/test/e2e/plugin/go.sum +++ b/test/e2e/plugin/go.sum @@ -8,14 +8,15 @@ 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/go-asn1-ber/asn1-ber v1.5.7 h1:DTX+lbVTWaTw1hQ+PbZPlnDZPEIs0SS/GCZAl535dDk= +github.com/go-asn1-ber/asn1-ber v1.5.7/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0= +github.com/go-ldap/ldap/v3 v3.4.10 h1:ot/iwPOhfpNVgB1o+AVXljizWZ9JTp7YF5oeyONmcJU= +github.com/go-ldap/ldap/v3 v3.4.10/go.mod h1:JXh4Uxgi40P6E9rdsYqpUtbW46D9UTjJ9QSwGRznplY= github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY= github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= github.com/golang-jwt/jwt/v4 v4.5.1 h1:JdqV9zKUdtaa9gdPlywC3aeoEsR681PlKC+4F5gQgeo= github.com/golang-jwt/jwt/v4 v4.5.1/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 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= @@ -37,14 +38,14 @@ 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.2.0-rc.1.0.20241129024749-95d89543c9f9 h1:FURo9xpGLKmghWCcWypCPQTlcOGKxzayeXacGfb8WUU= -github.com/notaryproject/notation-core-go v1.2.0-rc.1.0.20241129024749-95d89543c9f9/go.mod h1:Umjn4NKGmuHpVffMgKVcUnArNG3Qtd3duKYpPILUBg4= -github.com/notaryproject/notation-go v1.2.0-beta.1.0.20241202020354-95bac0082974 h1:EQ9DC25U7hWbBIOlwINxPhr9QEyixg1/Fo5ZZW+3JSU= -github.com/notaryproject/notation-go v1.2.0-beta.1.0.20241202020354-95bac0082974/go.mod h1:6a3/g7yD/8dxxBpimzUWthH8DLBrzHs4RTzdz9CALvw= +github.com/notaryproject/notation-core-go v1.2.0-rc.2 h1:0jOItalNwBNUhyuc5PPHQxO3jIZ5xRYq+IvRMQXNbuE= +github.com/notaryproject/notation-core-go v1.2.0-rc.2/go.mod h1:7aIcavfywFvBQoYyfVFJB501kt7Etqyubrt5mhJBG2c= +github.com/notaryproject/notation-go v1.2.0-beta.1.0.20250107003620-26ce0894a624 h1:JCJ+64H1A/aYhNaUak+1DV4dY2uL3L5GFMRLzrh9tDM= +github.com/notaryproject/notation-go v1.2.0-beta.1.0.20250107003620-26ce0894a624/go.mod h1:1QaHYG/UOeAYhfLBipsSxquu3BheRm7a+5RODcc5nQg= 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.1-0.20241030015323-90a141e7525c h1:bX6gGxFw9+DShmYTgbD+vr6neF1SoXIMUU2fDgdLsfA= -github.com/notaryproject/tspclient-go v0.2.1-0.20241030015323-90a141e7525c/go.mod h1:LGyA/6Kwd2FlM0uk8Vc5il3j0CddbWSHBj/4kxQDbjs= +github.com/notaryproject/tspclient-go v1.0.0-rc.1 h1:KcHxlqg6Adt4kzGLw012i0YMLlwGwToiR129c6IQ7Ys= +github.com/notaryproject/tspclient-go v1.0.0-rc.1/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= @@ -72,12 +73,16 @@ github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5t 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.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= 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.29.0 h1:L5SG1JTTXupVV3n6sUqMTeWbjAyfPwoda2DLX8J8FrQ= -golang.org/x/crypto v0.29.0/go.mod h1:+F4F4N5hv6v38hfeYwTdx20oUvLLc+QfrE9Ax9HtgRg= +golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= +golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U= +golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= 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.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4= golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -87,14 +92,19 @@ golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug 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.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= 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/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= +golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I= +golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= 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.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= +golang.org/x/sync v0.10.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= @@ -102,24 +112,34 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc 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.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE= 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.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= -golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58= +golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= +golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM= 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.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= 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/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= +golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= 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= diff --git a/test/e2e/run.sh b/test/e2e/run.sh index df89d4dca..af9aae7d7 100755 --- a/test/e2e/run.sh +++ b/test/e2e/run.sh @@ -117,6 +117,7 @@ export NOTATION_E2E_PLUGIN_PATH=$CWD/plugin/bin/$PLUGIN_NAME export NOTATION_E2E_PLUGIN_TAR_GZ_PATH=$CWD/plugin/bin/$PLUGIN_NAME.tar.gz export NOTATION_E2E_MALICIOUS_PLUGIN_ARCHIVE_PATH=$CWD/testdata/malicious-plugin export NOTATION_E2E_BLOB_PATH=$CWD/testdata/blob/blobFile +export NOTATION_E2E_BLOB_TRUST_POLICY_PATH=$CWD/testdata/blob/trustpolicies # run tests ginkgo -r -p -v diff --git a/test/e2e/suite/command/blob/verify.go b/test/e2e/suite/command/blob/verify.go new file mode 100644 index 000000000..e72c8a971 --- /dev/null +++ b/test/e2e/suite/command/blob/verify.go @@ -0,0 +1,45 @@ +// 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 blob + +import ( + "fmt" + "path/filepath" + + . "github.com/notaryproject/notation/test/e2e/internal/notation" + "github.com/notaryproject/notation/test/e2e/internal/utils" + . "github.com/notaryproject/notation/test/e2e/suite/common" + . "github.com/onsi/ginkgo/v2" +) + +var _ = Describe("notation blob verify", func() { + // Success cases + It("with blob verify", func() { + HostWithBlob(BaseBlobOptions(), func(notation *utils.ExecOpts, blobPath string, vhost *utils.VirtualHost) { + blobDir := filepath.Dir(blobPath) + notation.Exec("blob", "sign", "--force", "--signature-directory", blobDir, blobPath). + MatchKeyWords(SignSuccessfully). + MatchKeyWords("Signature file written to") + + notation.Exec("blob", "verify", "--signature", signatureFilepath(blobDir, blobPath, "jws"), blobPath). + MatchKeyWords(VerifySuccessfully) + }) + }) +}) + +func signatureFilepath(signatureDirectory, blobPath, signatureFormat string) string { + blobFilename := filepath.Base(blobPath) + signatureFilename := fmt.Sprintf("%s.%s.sig", blobFilename, signatureFormat) + return filepath.Join(signatureDirectory, signatureFilename) +} diff --git a/test/e2e/suite/command/policy.go b/test/e2e/suite/command/policy.go index 118939c58..c597228e9 100644 --- a/test/e2e/suite/command/policy.go +++ b/test/e2e/suite/command/policy.go @@ -35,7 +35,7 @@ var _ = Describe("trust policy maintainer", func() { }) It("should show error and hint if policy without read permission", func() { - Host(Opts(AddTrustPolicyOption(TrustPolicyName)), func(notation *utils.ExecOpts, artifact *Artifact, vhost *utils.VirtualHost) { + Host(Opts(AddTrustPolicyOption(TrustPolicyName, false)), func(notation *utils.ExecOpts, artifact *Artifact, vhost *utils.VirtualHost) { trustPolicyPath := vhost.AbsolutePath(NotationDirName, TrustPolicyName) os.Chmod(trustPolicyPath, 0200) notation.ExpectFailure(). @@ -47,7 +47,7 @@ var _ = Describe("trust policy maintainer", func() { It("should show exist policy", func() { content, err := os.ReadFile(filepath.Join(NotationE2ETrustPolicyDir, TrustPolicyName)) Expect(err).NotTo(HaveOccurred()) - Host(Opts(AddTrustPolicyOption(TrustPolicyName)), func(notation *utils.ExecOpts, artifact *Artifact, vhost *utils.VirtualHost) { + Host(Opts(AddTrustPolicyOption(TrustPolicyName, false)), func(notation *utils.ExecOpts, artifact *Artifact, vhost *utils.VirtualHost) { notation.Exec("policy", "show"). MatchContent(string(content)) }) @@ -57,7 +57,7 @@ var _ = Describe("trust policy maintainer", func() { policyName := "invalid_format_trustpolicy.json" content, err := os.ReadFile(filepath.Join(NotationE2ETrustPolicyDir, policyName)) Expect(err).NotTo(HaveOccurred()) - Host(Opts(AddTrustPolicyOption(policyName)), func(notation *utils.ExecOpts, artifact *Artifact, vhost *utils.VirtualHost) { + Host(Opts(AddTrustPolicyOption(policyName, false)), func(notation *utils.ExecOpts, artifact *Artifact, vhost *utils.VirtualHost) { notation.Exec("policy", "show"). MatchErrKeyWords("existing trust policy configuration is invalid"). MatchContent(string(content)) @@ -126,7 +126,7 @@ var _ = Describe("trust policy maintainer", func() { }) When("importing configuration with existing trust policy configuration", func() { - opts := Opts(AddTrustPolicyOption(TrustPolicyName)) + opts := Opts(AddTrustPolicyOption(TrustPolicyName, false)) It("should fail if no file path is provided", func() { Host(opts, func(notation *utils.ExecOpts, artifact *Artifact, vhost *utils.VirtualHost) { notation.ExpectFailure(). @@ -183,7 +183,7 @@ var _ = Describe("trust policy maintainer", func() { }) It("should skip confirmation if existing policy is malformed", func() { - Host(Opts(AddTrustPolicyOption("invalid_format_trustpolicy.json")), func(notation *utils.ExecOpts, artifact *Artifact, vhost *utils.VirtualHost) { + Host(Opts(AddTrustPolicyOption("invalid_format_trustpolicy.json", false)), func(notation *utils.ExecOpts, artifact *Artifact, vhost *utils.VirtualHost) { policyFileName := "skip_trustpolicy.json" notation.Exec("policy", "import", filepath.Join(NotationE2ETrustPolicyDir, policyFileName)).MatchKeyWords(). MatchKeyWords("Trust policy configuration imported successfully.") diff --git a/test/e2e/suite/command/sign.go b/test/e2e/suite/command/sign.go index d9cefd445..590c5a6ad 100644 --- a/test/e2e/suite/command/sign.go +++ b/test/e2e/suite/command/sign.go @@ -169,7 +169,7 @@ var _ = Describe("notation sign", func() { // copy the generated cert file and create the new trust policy for verify signature with generated new key. OldNotation(AuthOption("", ""), AddTrustStoreOption(keyName, vhost.AbsolutePath(NotationDirName, LocalKeysDirName, keyName+".crt")), - AddTrustPolicyOption("generate_test_trustpolicy.json"), + AddTrustPolicyOption("generate_test_trustpolicy.json", false), ).Exec("verify", artifact.ReferenceWithTag()). MatchKeyWords(VerifySuccessfully) }) diff --git a/test/e2e/suite/scenario/quickstart.go b/test/e2e/suite/scenario/quickstart.go index b04f5b22e..96161ecb4 100644 --- a/test/e2e/suite/scenario/quickstart.go +++ b/test/e2e/suite/scenario/quickstart.go @@ -85,7 +85,7 @@ var _ = Describe("notation quickstart E2E test", Ordered, func() { }) It("Create a trust policy", func() { - vhost.SetOption(AddTrustPolicyOption("quickstart_trustpolicy.json")) + vhost.SetOption(AddTrustPolicyOption("quickstart_trustpolicy.json", false)) validator.CheckFileExist(vhost.AbsolutePath(NotationDirName, TrustPolicyName)) }) diff --git a/test/e2e/suite/trustpolicy/multi_statements.go b/test/e2e/suite/trustpolicy/multi_statements.go index 6364123b6..b9ae2bcc6 100644 --- a/test/e2e/suite/trustpolicy/multi_statements.go +++ b/test/e2e/suite/trustpolicy/multi_statements.go @@ -25,7 +25,7 @@ var _ = Describe("notation trust policy multi-statements test", func() { Host(BaseOptions(), func(notation *utils.ExecOpts, _ *Artifact, vhost *utils.VirtualHost) { artifact := GenerateArtifact("", "test-repo8") // update trustpolicy.json - vhost.SetOption(AddTrustPolicyOption("multi_statements_with_the_same_registry_scope_trustpolicy.json")) + vhost.SetOption(AddTrustPolicyOption("multi_statements_with_the_same_registry_scope_trustpolicy.json", false)) // test localhost:5000/test-repo notation.Exec("sign", artifact.ReferenceWithDigest()).MatchKeyWords(SignSuccessfully) @@ -38,7 +38,7 @@ var _ = Describe("notation trust policy multi-statements test", func() { Host(BaseOptions(), func(notation *utils.ExecOpts, _ *Artifact, vhost *utils.VirtualHost) { artifact := GenerateArtifact("e2e-valid-signature", "test-repo9") // update trustpolicy.json - vhost.SetOption(AddTrustPolicyOption("multi_statements_with_wildcard_registry_scope_trustpolicy.json")) + vhost.SetOption(AddTrustPolicyOption("multi_statements_with_wildcard_registry_scope_trustpolicy.json", false)) // test localhost:5000/test-repo notation.Exec("sign", artifact.ReferenceWithDigest()).MatchKeyWords(SignSuccessfully) @@ -51,7 +51,7 @@ var _ = Describe("notation trust policy multi-statements test", func() { Host(BaseOptions(), func(notation *utils.ExecOpts, _ *Artifact, vhost *utils.VirtualHost) { artifact := GenerateArtifact("", "test-repo10") // update trustpolicy.json - vhost.SetOption(AddTrustPolicyOption("multi_statements_with_the_same_name_trustpolicy.json")) + vhost.SetOption(AddTrustPolicyOption("multi_statements_with_the_same_name_trustpolicy.json", false)) // test localhost:5000/test-repo notation.Exec("sign", artifact.ReferenceWithDigest()).MatchKeyWords(SignSuccessfully) @@ -63,7 +63,7 @@ var _ = Describe("notation trust policy multi-statements test", func() { It("multiple statements with multi-wildcard registry scopes", func() { Host(BaseOptions(), func(notation *utils.ExecOpts, artifact *Artifact, vhost *utils.VirtualHost) { // update trustpolicy.json - vhost.SetOption(AddTrustPolicyOption("multi_statements_with_multi_wildcard_registry_scope_trustpolicy.json")) + vhost.SetOption(AddTrustPolicyOption("multi_statements_with_multi_wildcard_registry_scope_trustpolicy.json", false)) // test localhost:5000/test-repo notation.Exec("sign", artifact.ReferenceWithDigest()).MatchKeyWords(SignSuccessfully) diff --git a/test/e2e/suite/trustpolicy/registry_scope.go b/test/e2e/suite/trustpolicy/registry_scope.go index 7eb19a5f4..0e4cb0725 100644 --- a/test/e2e/suite/trustpolicy/registry_scope.go +++ b/test/e2e/suite/trustpolicy/registry_scope.go @@ -29,7 +29,7 @@ var _ = Describe("notation trust policy registryScope test", func() { It("empty registryScope", func() { Host(BaseOptions(), func(notation *utils.ExecOpts, artifact *Artifact, vhost *utils.VirtualHost) { // update trustpolicy.json - vhost.SetOption(AddTrustPolicyOption("empty_registry_scope_trustpolicy.json")) + vhost.SetOption(AddTrustPolicyOption("empty_registry_scope_trustpolicy.json", false)) // test localhost:5000/test-repo OldNotation().Exec("sign", artifact.ReferenceWithDigest()).MatchKeyWords(SignSuccessfully) @@ -41,7 +41,7 @@ var _ = Describe("notation trust policy registryScope test", func() { It("malformed registryScope", func() { Host(BaseOptions(), func(notation *utils.ExecOpts, artifact *Artifact, vhost *utils.VirtualHost) { // update trustpolicy.json - vhost.SetOption(AddTrustPolicyOption("malformed_registry_scope_trustpolicy.json")) + vhost.SetOption(AddTrustPolicyOption("malformed_registry_scope_trustpolicy.json", false)) OldNotation().Exec("sign", artifact.ReferenceWithDigest()).MatchKeyWords(SignSuccessfully) notation.ExpectFailure().Exec("verify", artifact.ReferenceWithDigest()). @@ -52,7 +52,7 @@ var _ = Describe("notation trust policy registryScope test", func() { It("registryScope with a repository", func() { Host(BaseOptions(), func(notation *utils.ExecOpts, _ *Artifact, vhost *utils.VirtualHost) { // update trustpolicy.json - vhost.SetOption(AddTrustPolicyOption("registry_scope_trustpolicy.json")) + vhost.SetOption(AddTrustPolicyOption("registry_scope_trustpolicy.json", false)) // generate an artifact with given repository name artifact := GenerateArtifact("", "test-repo") @@ -66,7 +66,7 @@ var _ = Describe("notation trust policy registryScope test", func() { It("registryScope with multiple repositories", func() { Host(BaseOptions(), func(notation *utils.ExecOpts, _ *Artifact, vhost *utils.VirtualHost) { // update trustpolicy.json - vhost.SetOption(AddTrustPolicyOption("multiple_registry_scope_trustpolicy.json")) + vhost.SetOption(AddTrustPolicyOption("multiple_registry_scope_trustpolicy.json", false)) // generate an artifact with given repository name artifact2 := GenerateArtifact("", "test-repo2") @@ -85,7 +85,7 @@ var _ = Describe("notation trust policy registryScope test", func() { It("registryScope with any(*) repository", func() { Host(BaseOptions(), func(notation *utils.ExecOpts, _ *Artifact, vhost *utils.VirtualHost) { // update trustpolicy.json - vhost.SetOption(AddTrustPolicyOption("any_registry_scope_trust_policy.json")) + vhost.SetOption(AddTrustPolicyOption("any_registry_scope_trust_policy.json", false)) // generate an artifact with given repository name artifact4 := GenerateArtifact("", "test-repo4") @@ -104,7 +104,7 @@ var _ = Describe("notation trust policy registryScope test", func() { It("overlapped registryScope", func() { Host(BaseOptions(), func(notation *utils.ExecOpts, _ *Artifact, vhost *utils.VirtualHost) { // update trustpolicy.json - vhost.SetOption(AddTrustPolicyOption("overlapped_registry_scope_trustpolicy.json")) + vhost.SetOption(AddTrustPolicyOption("overlapped_registry_scope_trustpolicy.json", false)) artifact := GenerateArtifact("", "test-repo6") @@ -118,7 +118,7 @@ var _ = Describe("notation trust policy registryScope test", func() { It("wildcard plus specific repo registryScope", func() { Host(BaseOptions(), func(notation *utils.ExecOpts, _ *Artifact, vhost *utils.VirtualHost) { // update trustpolicy.json - vhost.SetOption(AddTrustPolicyOption("wildcard_plus_other_registry_scope_trustpolicy.json")) + vhost.SetOption(AddTrustPolicyOption("wildcard_plus_other_registry_scope_trustpolicy.json", false)) artifact := GenerateArtifact("", "test-repo7") @@ -132,7 +132,7 @@ var _ = Describe("notation trust policy registryScope test", func() { It("invalid registryScope", func() { Host(BaseOptions(), func(notation *utils.ExecOpts, artifact *Artifact, vhost *utils.VirtualHost) { // update trustpolicy.json - vhost.SetOption(AddTrustPolicyOption("invalid_registry_scope_trustpolicy.json")) + vhost.SetOption(AddTrustPolicyOption("invalid_registry_scope_trustpolicy.json", false)) // test localhost:5000/test-repo OldNotation().Exec("sign", artifact.ReferenceWithDigest()).MatchKeyWords(SignSuccessfully) diff --git a/test/e2e/suite/trustpolicy/trust_store.go b/test/e2e/suite/trustpolicy/trust_store.go index d01a2fae0..9f1a90ecd 100644 --- a/test/e2e/suite/trustpolicy/trust_store.go +++ b/test/e2e/suite/trustpolicy/trust_store.go @@ -25,7 +25,7 @@ import ( var _ = Describe("notation trust policy trust store test", func() { It("unset trust store", func() { Host(BaseOptions(), func(notation *utils.ExecOpts, _ *Artifact, vhost *utils.VirtualHost) { - vhost.SetOption(AddTrustPolicyOption("unset_trust_store_trustpolicy.json")) + vhost.SetOption(AddTrustPolicyOption("unset_trust_store_trustpolicy.json", false)) artifact := GenerateArtifact("e2e-valid-signature", "") @@ -36,7 +36,7 @@ var _ = Describe("notation trust policy trust store test", func() { It("invalid trust store", func() { Host(BaseOptions(), func(notation *utils.ExecOpts, _ *Artifact, vhost *utils.VirtualHost) { - vhost.SetOption(AddTrustPolicyOption("invalid_trust_store_trustpolicy.json")) + vhost.SetOption(AddTrustPolicyOption("invalid_trust_store_trustpolicy.json", false)) artifact := GenerateArtifact("e2e-valid-signature", "") @@ -47,7 +47,7 @@ var _ = Describe("notation trust policy trust store test", func() { It("malformed trust store", func() { Host(BaseOptions(), func(notation *utils.ExecOpts, _ *Artifact, vhost *utils.VirtualHost) { - vhost.SetOption(AddTrustPolicyOption("malformed_trust_store_trustpolicy.json")) + vhost.SetOption(AddTrustPolicyOption("malformed_trust_store_trustpolicy.json", false)) artifact := GenerateArtifact("e2e-valid-signature", "") @@ -58,7 +58,7 @@ var _ = Describe("notation trust policy trust store test", func() { It("wildcard (malformed) trust store", func() { Host(BaseOptions(), func(notation *utils.ExecOpts, _ *Artifact, vhost *utils.VirtualHost) { - vhost.SetOption(AddTrustPolicyOption("wildcard_trust_store_trustpolicy.json")) + vhost.SetOption(AddTrustPolicyOption("wildcard_trust_store_trustpolicy.json", false)) artifact := GenerateArtifact("e2e-valid-signature", "") @@ -79,7 +79,7 @@ var _ = Describe("notation trust policy trust store test", func() { // setup multiple trust store vhost.SetOption(AuthOption("", ""), - AddTrustPolicyOption("multiple_trust_store_trustpolicy.json"), + AddTrustPolicyOption("multiple_trust_store_trustpolicy.json", false), AddTrustStoreOption("e2e-new", filepath.Join(NotationE2ELocalKeysDir, "new_e2e.crt")), AddTrustStoreOption("e2e", filepath.Join(NotationE2ELocalKeysDir, "e2e.crt")), ) @@ -103,7 +103,7 @@ var _ = Describe("notation trust policy trust store test", func() { // setup overlapped trust store vhost.SetOption(AuthOption("", ""), - AddTrustPolicyOption("overlapped_trust_store_trustpolicy.json"), + AddTrustPolicyOption("overlapped_trust_store_trustpolicy.json", false), AddTrustStoreOption("e2e", filepath.Join(NotationE2ELocalKeysDir, "e2e.crt"))) notation.ExpectFailure().Exec("verify", artifact.ReferenceWithDigest(), "-v"). diff --git a/test/e2e/suite/trustpolicy/trusted_identity.go b/test/e2e/suite/trustpolicy/trusted_identity.go index e37637340..e822eb1ce 100644 --- a/test/e2e/suite/trustpolicy/trusted_identity.go +++ b/test/e2e/suite/trustpolicy/trusted_identity.go @@ -25,7 +25,7 @@ import ( var _ = Describe("notation trust policy trusted identity test", func() { It("with unset trusted identity", func() { Host(BaseOptions(), func(notation *utils.ExecOpts, _ *Artifact, vhost *utils.VirtualHost) { - vhost.SetOption(AddTrustPolicyOption("unset_trusted_identity_trustpolicy.json")) + vhost.SetOption(AddTrustPolicyOption("unset_trusted_identity_trustpolicy.json", false)) artifact := GenerateArtifact("e2e-valid-signature", "") notation.ExpectFailure().Exec("verify", artifact.ReferenceWithDigest(), "-v"). @@ -35,7 +35,7 @@ var _ = Describe("notation trust policy trusted identity test", func() { It("with valid trusted identity", func() { Host(BaseOptions(), func(notation *utils.ExecOpts, _ *Artifact, vhost *utils.VirtualHost) { - vhost.SetOption(AddTrustPolicyOption("valid_trusted_identity_trustpolicy.json")) + vhost.SetOption(AddTrustPolicyOption("valid_trusted_identity_trustpolicy.json", false)) artifact := GenerateArtifact("e2e-valid-signature", "") notation.Exec("verify", artifact.ReferenceWithDigest(), "-v"). @@ -45,7 +45,7 @@ var _ = Describe("notation trust policy trusted identity test", func() { It("with invalid trusted identity", func() { Host(BaseOptions(), func(notation *utils.ExecOpts, _ *Artifact, vhost *utils.VirtualHost) { - vhost.SetOption(AddTrustPolicyOption("invalid_trusted_identity_trustpolicy.json")) + vhost.SetOption(AddTrustPolicyOption("invalid_trusted_identity_trustpolicy.json", false)) artifact := GenerateArtifact("e2e-valid-signature", "") notation.ExpectFailure().Exec("verify", artifact.ReferenceWithDigest(), "-v"). @@ -56,7 +56,7 @@ var _ = Describe("notation trust policy trusted identity test", func() { It("with malformed trusted identity", func() { Host(BaseOptions(), func(notation *utils.ExecOpts, _ *Artifact, vhost *utils.VirtualHost) { - vhost.SetOption(AddTrustPolicyOption("malformed_trusted_identity_trustpolicy.json")) + vhost.SetOption(AddTrustPolicyOption("malformed_trusted_identity_trustpolicy.json", false)) artifact := GenerateArtifact("e2e-valid-signature", "") notation.ExpectFailure().Exec("verify", artifact.ReferenceWithDigest(), "-v"). @@ -66,7 +66,7 @@ var _ = Describe("notation trust policy trusted identity test", func() { It("with empty trusted identity", func() { Host(BaseOptions(), func(notation *utils.ExecOpts, _ *Artifact, vhost *utils.VirtualHost) { - vhost.SetOption(AddTrustPolicyOption("empty_trusted_identity_trustpolicy.json")) + vhost.SetOption(AddTrustPolicyOption("empty_trusted_identity_trustpolicy.json", false)) artifact := GenerateArtifact("e2e-valid-signature", "") notation.ExpectFailure().Exec("verify", artifact.ReferenceWithDigest(), "-v"). @@ -86,7 +86,7 @@ var _ = Describe("notation trust policy trusted identity test", func() { // setup multiple trusted identity vhost.SetOption(AuthOption("", ""), - AddTrustPolicyOption("multiple_trusted_identity_trustpolicy.json"), + AddTrustPolicyOption("multiple_trusted_identity_trustpolicy.json", false), AddTrustStoreOption("e2e", filepath.Join(NotationE2ELocalKeysDir, "new_e2e.crt")), AddTrustStoreOption("e2e", filepath.Join(NotationE2ELocalKeysDir, "e2e.crt")), ) @@ -101,7 +101,7 @@ var _ = Describe("notation trust policy trusted identity test", func() { It("with overlapped trusted identities", func() { Host(BaseOptions(), func(notation *utils.ExecOpts, _ *Artifact, vhost *utils.VirtualHost) { - vhost.SetOption(AddTrustPolicyOption("overlapped_trusted_identity_trustpolicy.json")) + vhost.SetOption(AddTrustPolicyOption("overlapped_trusted_identity_trustpolicy.json", false)) artifact := GenerateArtifact("e2e-valid-signature", "") notation.ExpectFailure().Exec("verify", artifact.ReferenceWithDigest(), "-v"). @@ -111,7 +111,7 @@ var _ = Describe("notation trust policy trusted identity test", func() { It("with wildcard plus other trusted identities", func() { Host(BaseOptions(), func(notation *utils.ExecOpts, _ *Artifact, vhost *utils.VirtualHost) { - vhost.SetOption(AddTrustPolicyOption("wildcard_plus_other_trusted_identity_trustpolicy.json")) + vhost.SetOption(AddTrustPolicyOption("wildcard_plus_other_trusted_identity_trustpolicy.json", false)) artifact := GenerateArtifact("e2e-valid-signature", "") notation.ExpectFailure().Exec("verify", artifact.ReferenceWithDigest(), "-v"). @@ -121,7 +121,7 @@ var _ = Describe("notation trust policy trusted identity test", func() { It("with trusted identities missing organization", func() { Host(BaseOptions(), func(notation *utils.ExecOpts, _ *Artifact, vhost *utils.VirtualHost) { - vhost.SetOption(AddTrustPolicyOption("missing_organization_trusted_identity_trustpolicy.json")) + vhost.SetOption(AddTrustPolicyOption("missing_organization_trusted_identity_trustpolicy.json", false)) artifact := GenerateArtifact("e2e-valid-signature", "") notation.ExpectFailure().Exec("verify", artifact.ReferenceWithDigest(), "-v"). @@ -131,7 +131,7 @@ var _ = Describe("notation trust policy trusted identity test", func() { It("with trusted identities missing state", func() { Host(BaseOptions(), func(notation *utils.ExecOpts, _ *Artifact, vhost *utils.VirtualHost) { - vhost.SetOption(AddTrustPolicyOption("missing_state_trusted_identity_trustpolicy.json")) + vhost.SetOption(AddTrustPolicyOption("missing_state_trusted_identity_trustpolicy.json", false)) artifact := GenerateArtifact("e2e-valid-signature", "") notation.ExpectFailure().Exec("verify", artifact.ReferenceWithDigest(), "-v"). @@ -141,7 +141,7 @@ var _ = Describe("notation trust policy trusted identity test", func() { It("with trusted identities missing country", func() { Host(BaseOptions(), func(notation *utils.ExecOpts, _ *Artifact, vhost *utils.VirtualHost) { - vhost.SetOption(AddTrustPolicyOption("missing_country_trusted_identity_trustpolicy.json")) + vhost.SetOption(AddTrustPolicyOption("missing_country_trusted_identity_trustpolicy.json", false)) artifact := GenerateArtifact("e2e-valid-signature", "") notation.ExpectFailure().Exec("verify", artifact.ReferenceWithDigest(), "-v"). diff --git a/test/e2e/suite/trustpolicy/verification_level.go b/test/e2e/suite/trustpolicy/verification_level.go index 3f6c1197c..0ebaf3ce7 100644 --- a/test/e2e/suite/trustpolicy/verification_level.go +++ b/test/e2e/suite/trustpolicy/verification_level.go @@ -38,7 +38,7 @@ var _ = Describe("notation trust policy verification level test", func() { artifact := GenerateArtifact("e2e-with-expired-cert", "") vhost.SetOption(AuthOption("", ""), - AddTrustPolicyOption("trustpolicy.json"), + AddTrustPolicyOption("trustpolicy.json", false), AddTrustStoreOption("e2e", filepath.Join(NotationE2EConfigPath, "localkeys", "expired_e2e.crt")), ) @@ -51,7 +51,7 @@ var _ = Describe("notation trust policy verification level test", func() { It("strict level with invalid authenticity", func() { Host(nil, func(notation *utils.ExecOpts, _ *Artifact, vhost *utils.VirtualHost) { vhost.SetOption(AuthOption("", ""), - AddTrustPolicyOption("trustpolicy.json"), + AddTrustPolicyOption("trustpolicy.json", false), AddTrustStoreOption("e2e", filepath.Join(NotationE2ELocalKeysDir, "new_e2e.crt")), ) @@ -77,7 +77,7 @@ var _ = Describe("notation trust policy verification level test", func() { It("permissive level with expired signature", func() { Host(BaseOptions(), func(notation *utils.ExecOpts, _ *Artifact, vhost *utils.VirtualHost) { - vhost.SetOption(AddTrustPolicyOption("permissive_trustpolicy.json")) + vhost.SetOption(AddTrustPolicyOption("permissive_trustpolicy.json", false)) artifact := GenerateArtifact("e2e-expired-signature", "") @@ -92,7 +92,7 @@ var _ = Describe("notation trust policy verification level test", func() { artifact := GenerateArtifact("e2e-with-expired-cert", "") vhost.SetOption(AuthOption("", ""), - AddTrustPolicyOption("permissive_trustpolicy.json"), + AddTrustPolicyOption("permissive_trustpolicy.json", false), AddTrustStoreOption("e2e", filepath.Join(NotationE2EConfigPath, "localkeys", "expired_e2e.crt")), ) @@ -106,7 +106,7 @@ var _ = Describe("notation trust policy verification level test", func() { It("permissive level with invalid authenticity", func() { Host(nil, func(notation *utils.ExecOpts, _ *Artifact, vhost *utils.VirtualHost) { vhost.SetOption(AuthOption("", ""), - AddTrustPolicyOption("permissive_trustpolicy.json"), + AddTrustPolicyOption("permissive_trustpolicy.json", false), AddTrustStoreOption("e2e", filepath.Join(NotationE2ELocalKeysDir, "new_e2e.crt")), ) @@ -122,7 +122,7 @@ var _ = Describe("notation trust policy verification level test", func() { It("permissive level with invalid integrity", func() { Host(BaseOptions(), func(notation *utils.ExecOpts, _ *Artifact, vhost *utils.VirtualHost) { - vhost.SetOption(AddTrustPolicyOption("permissive_trustpolicy.json")) + vhost.SetOption(AddTrustPolicyOption("permissive_trustpolicy.json", false)) artifact := GenerateArtifact("e2e-invalid-signature", "") @@ -134,7 +134,7 @@ var _ = Describe("notation trust policy verification level test", func() { It("audit level with expired signature", func() { Host(BaseOptions(), func(notation *utils.ExecOpts, _ *Artifact, vhost *utils.VirtualHost) { - vhost.SetOption(AddTrustPolicyOption("audit_trustpolicy.json")) + vhost.SetOption(AddTrustPolicyOption("audit_trustpolicy.json", false)) artifact := GenerateArtifact("e2e-expired-signature", "") @@ -150,7 +150,7 @@ var _ = Describe("notation trust policy verification level test", func() { artifact := GenerateArtifact("e2e-with-expired-cert", "") vhost.SetOption(AuthOption("", ""), - AddTrustPolicyOption("audit_trustpolicy.json"), + AddTrustPolicyOption("audit_trustpolicy.json", false), AddTrustStoreOption("e2e", filepath.Join(NotationE2EConfigPath, "localkeys", "expired_e2e.crt")), ) @@ -164,7 +164,7 @@ var _ = Describe("notation trust policy verification level test", func() { It("audit level with invalid authenticity", func() { Host(nil, func(notation *utils.ExecOpts, _ *Artifact, vhost *utils.VirtualHost) { vhost.SetOption(AuthOption("", ""), - AddTrustPolicyOption("audit_trustpolicy.json"), + AddTrustPolicyOption("audit_trustpolicy.json", false), AddTrustStoreOption("e2e", filepath.Join(NotationE2ELocalKeysDir, "new_e2e.crt")), ) @@ -181,7 +181,7 @@ var _ = Describe("notation trust policy verification level test", func() { It("audit level with invalid integrity", func() { Host(BaseOptions(), func(notation *utils.ExecOpts, _ *Artifact, vhost *utils.VirtualHost) { - vhost.SetOption(AddTrustPolicyOption("audit_trustpolicy.json")) + vhost.SetOption(AddTrustPolicyOption("audit_trustpolicy.json", false)) artifact := GenerateArtifact("e2e-invalid-signature", "") @@ -193,7 +193,7 @@ var _ = Describe("notation trust policy verification level test", func() { It("skip level with invalid integrity", func() { Host(BaseOptions(), func(notation *utils.ExecOpts, _ *Artifact, vhost *utils.VirtualHost) { - vhost.SetOption(AddTrustPolicyOption("skip_trustpolicy.json")) + vhost.SetOption(AddTrustPolicyOption("skip_trustpolicy.json", false)) artifact := GenerateArtifact("e2e-invalid-signature", "") @@ -204,7 +204,7 @@ var _ = Describe("notation trust policy verification level test", func() { It("strict level with Expiry overridden as log level", func() { Host(BaseOptions(), func(notation *utils.ExecOpts, _ *Artifact, vhost *utils.VirtualHost) { - vhost.SetOption(AddTrustPolicyOption("override_strict_trustpolicy.json")) + vhost.SetOption(AddTrustPolicyOption("override_strict_trustpolicy.json", false)) artifact := GenerateArtifact("e2e-expired-signature", "") @@ -220,7 +220,7 @@ var _ = Describe("notation trust policy verification level test", func() { artifact := GenerateArtifact("e2e-with-expired-cert", "") vhost.SetOption(AuthOption("", ""), - AddTrustPolicyOption("override_strict_trustpolicy.json"), + AddTrustPolicyOption("override_strict_trustpolicy.json", false), AddTrustStoreOption("e2e", filepath.Join(NotationE2EConfigPath, "localkeys", "expired_e2e.crt")), ) @@ -234,7 +234,7 @@ var _ = Describe("notation trust policy verification level test", func() { It("strict level with Authenticity overridden as log level", func() { Host(nil, func(notation *utils.ExecOpts, _ *Artifact, vhost *utils.VirtualHost) { vhost.SetOption(AuthOption("", ""), - AddTrustPolicyOption("override_strict_trustpolicy.json"), + AddTrustPolicyOption("override_strict_trustpolicy.json", false), AddTrustStoreOption("e2e", filepath.Join(NotationE2ELocalKeysDir, "new_e2e.crt")), ) @@ -251,7 +251,7 @@ var _ = Describe("notation trust policy verification level test", func() { It("permissive level with Expiry overridden as enforce level", func() { Host(BaseOptions(), func(notation *utils.ExecOpts, _ *Artifact, vhost *utils.VirtualHost) { - vhost.SetOption(AddTrustPolicyOption("override_permissive_trustpolicy.json")) + vhost.SetOption(AddTrustPolicyOption("override_permissive_trustpolicy.json", false)) artifact := GenerateArtifact("e2e-expired-signature", "") @@ -263,12 +263,12 @@ var _ = Describe("notation trust policy verification level test", func() { It("permissive level with Authentic timestamp overridden as enforce level", func() { Host(nil, func(notation *utils.ExecOpts, _ *Artifact, vhost *utils.VirtualHost) { - vhost.SetOption(AddTrustPolicyOption("override_permissive_trustpolicy.json")) + vhost.SetOption(AddTrustPolicyOption("override_permissive_trustpolicy.json", false)) artifact := GenerateArtifact("e2e-with-expired-cert", "") vhost.SetOption(AuthOption("", ""), - AddTrustPolicyOption("trustpolicy.json"), + AddTrustPolicyOption("trustpolicy.json", false), AddTrustStoreOption("e2e", filepath.Join(NotationE2EConfigPath, "localkeys", "expired_e2e.crt")), ) @@ -281,7 +281,7 @@ var _ = Describe("notation trust policy verification level test", func() { It("permissive level with Authenticity overridden as log level", func() { Host(nil, func(notation *utils.ExecOpts, _ *Artifact, vhost *utils.VirtualHost) { vhost.SetOption(AuthOption("", ""), - AddTrustPolicyOption("override_permissive_trustpolicy.json"), + AddTrustPolicyOption("override_permissive_trustpolicy.json", false), AddTrustStoreOption("e2e", filepath.Join(NotationE2ELocalKeysDir, "new_e2e.crt")), ) @@ -297,7 +297,7 @@ var _ = Describe("notation trust policy verification level test", func() { It("permissive level with Integrity overridden as log level", func() { Host(nil, func(notation *utils.ExecOpts, _ *Artifact, vhost *utils.VirtualHost) { vhost.SetOption(AuthOption("", ""), - AddTrustPolicyOption("override_integrity_for_permissive_trustpolicy.json"), + AddTrustPolicyOption("override_integrity_for_permissive_trustpolicy.json", false), AddTrustStoreOption("e2e", filepath.Join(NotationE2ELocalKeysDir, "new_e2e.crt")), ) @@ -310,7 +310,7 @@ var _ = Describe("notation trust policy verification level test", func() { It("audit level with Expiry overridden as enforce level", func() { Host(BaseOptions(), func(notation *utils.ExecOpts, _ *Artifact, vhost *utils.VirtualHost) { - vhost.SetOption(AddTrustPolicyOption("override_audit_trustpolicy.json")) + vhost.SetOption(AddTrustPolicyOption("override_audit_trustpolicy.json", false)) artifact := GenerateArtifact("e2e-expired-signature", "") @@ -322,12 +322,12 @@ var _ = Describe("notation trust policy verification level test", func() { It("audit level with Authentic timestamp overridden as enforce level", func() { Host(nil, func(notation *utils.ExecOpts, _ *Artifact, vhost *utils.VirtualHost) { - vhost.SetOption(AddTrustPolicyOption("override_audit_trustpolicy.json")) + vhost.SetOption(AddTrustPolicyOption("override_audit_trustpolicy.json", false)) artifact := GenerateArtifact("e2e-with-expired-cert", "") vhost.SetOption(AuthOption("", ""), - AddTrustPolicyOption("trustpolicy.json"), + AddTrustPolicyOption("trustpolicy.json", false), AddTrustStoreOption("e2e", filepath.Join(NotationE2EConfigPath, "localkeys", "expired_e2e.crt")), ) @@ -340,7 +340,7 @@ var _ = Describe("notation trust policy verification level test", func() { It("audit level with Authenticity overridden as enforce level", func() { Host(nil, func(notation *utils.ExecOpts, _ *Artifact, vhost *utils.VirtualHost) { vhost.SetOption(AuthOption("", ""), - AddTrustPolicyOption("override_audit_trustpolicy.json"), + AddTrustPolicyOption("override_audit_trustpolicy.json", false), AddTrustStoreOption("e2e", filepath.Join(NotationE2ELocalKeysDir, "new_e2e.crt")), ) diff --git a/test/e2e/testdata/blob/trustpolicies/trustpolicy.blob.json b/test/e2e/testdata/blob/trustpolicies/trustpolicy.blob.json new file mode 100644 index 000000000..85429cfb4 --- /dev/null +++ b/test/e2e/testdata/blob/trustpolicies/trustpolicy.blob.json @@ -0,0 +1,26 @@ +{ + "version": "1.0", + "trustPolicies": [ + { + "name": "test-blob-statement", + "signatureVerification": { + "level" : "strict" + }, + "trustStores": [ "ca:e2e" ], + "trustedIdentities": [ + "*" + ] + }, + { + "name": "test-blob-global-statement", + "signatureVerification": { + "level" : "audit" + }, + "trustStores": [ "ca:e2e" ], + "trustedIdentities": [ + "*" + ], + "globalPolicy": true + } + ] +} From d6eab1bf587c1c08e9f4a66277198d2f5ababd46 Mon Sep 17 00:00:00 2001 From: Patrick Zheng Date: Thu, 9 Jan 2025 16:47:07 +0800 Subject: [PATCH 23/53] fix test Signed-off-by: Patrick Zheng --- cmd/notation/blob/verify.go | 23 +++++++++++++++++++---- test/e2e/suite/command/blob/sign.go | 14 +++++++++++--- test/e2e/suite/command/blob/verify.go | 22 +++++++++++++++++++++- 3 files changed, 51 insertions(+), 8 deletions(-) diff --git a/cmd/notation/blob/verify.go b/cmd/notation/blob/verify.go index ba56a36b6..e39834301 100644 --- a/cmd/notation/blob/verify.go +++ b/cmd/notation/blob/verify.go @@ -15,10 +15,13 @@ package blob import ( "errors" + "fmt" "os" "path/filepath" "strings" + "github.com/notaryproject/notation-core-go/signature/cose" + "github.com/notaryproject/notation-core-go/signature/jws" "github.com/notaryproject/notation-go" "github.com/notaryproject/notation/internal/cmd" "github.com/notaryproject/notation/internal/ioutil" @@ -120,9 +123,13 @@ func runVerify(command *cobra.Command, cmdOpts *blobVerifyOpts) error { return err } + signatureMediaType, err := parseSignatureMediaType(cmdOpts.signaturePath) + if err != nil { + return err + } verifyBlobOpts := notation.VerifyBlobOptions{ BlobVerifierVerifyOptions: notation.BlobVerifierVerifyOptions{ - SignatureMediaType: signatureFormat(cmdOpts.signaturePath), + SignatureMediaType: signatureMediaType, PluginConfig: pluginConfigs, UserMetadata: userMetadata, TrustPolicyName: cmdOpts.policyStatementName, @@ -139,8 +146,16 @@ func runVerify(command *cobra.Command, cmdOpts *blobVerifyOpts) error { return nil } -// signatureFormat returns the format of the signature file -func signatureFormat(signaturePath string) string { +// parseSignatureMediaType returns the media type of the signature file. +// `application/jose+json` and `application/cose` are supported. +func parseSignatureMediaType(signaturePath string) (string, error) { signatureFileName := filepath.Base(signaturePath) - return strings.Split(signatureFileName, ".")[1] + format := strings.Split(signatureFileName, ".")[1] + switch format { + case "cose": + return cose.MediaTypeEnvelope, nil + case "jws": + return jws.MediaTypeEnvelope, nil + } + return "", fmt.Errorf("unsupported signature format %s", format) } diff --git a/test/e2e/suite/command/blob/sign.go b/test/e2e/suite/command/blob/sign.go index 867219b58..ef0ea76cf 100644 --- a/test/e2e/suite/command/blob/sign.go +++ b/test/e2e/suite/command/blob/sign.go @@ -128,12 +128,20 @@ var _ = Describe("notation blob sign", func() { It("with no permission to read the blob file", func() { HostWithBlob(BaseOptions(), func(notation *utils.ExecOpts, blobPath string, vhost *utils.VirtualHost) { - if err := os.Chmod(blobPath, 0000); err != nil { + blobDir := filepath.Dir(blobPath) + noPermissionBlobPath := filepath.Join(blobDir, "noPermissionBlob") + newBlobFile, err := os.Create(noPermissionBlobPath) + if err != nil { + Fail(err.Error()) + } + defer newBlobFile.Close() + + if err := os.Chmod(noPermissionBlobPath, 0000); err != nil { Fail(err.Error()) } - defer os.Chmod(blobPath, 0700) + defer os.Chmod(noPermissionBlobPath, 0700) - notation.ExpectFailure().Exec("blob", "sign", blobPath). + notation.ExpectFailure().Exec("blob", "sign", noPermissionBlobPath). MatchErrKeyWords("permission denied") }) }) diff --git a/test/e2e/suite/command/blob/verify.go b/test/e2e/suite/command/blob/verify.go index e72c8a971..c6c47fb20 100644 --- a/test/e2e/suite/command/blob/verify.go +++ b/test/e2e/suite/command/blob/verify.go @@ -16,7 +16,10 @@ package blob import ( "fmt" "path/filepath" + "strings" + "github.com/notaryproject/notation-core-go/signature/cose" + "github.com/notaryproject/notation-core-go/signature/jws" . "github.com/notaryproject/notation/test/e2e/internal/notation" "github.com/notaryproject/notation/test/e2e/internal/utils" . "github.com/notaryproject/notation/test/e2e/suite/common" @@ -32,12 +35,29 @@ var _ = Describe("notation blob verify", func() { MatchKeyWords(SignSuccessfully). MatchKeyWords("Signature file written to") - notation.Exec("blob", "verify", "--signature", signatureFilepath(blobDir, blobPath, "jws"), blobPath). + signaturePath := signatureFilepath(blobDir, blobPath, "jws") + signatureMediaType, err := parseSignatureMediaType(signaturePath) + if err != nil { + Fail(err.Error()) + } + notation.Exec("blob", "verify", "--signature", signatureMediaType, blobPath). MatchKeyWords(VerifySuccessfully) }) }) }) +func parseSignatureMediaType(signaturePath string) (string, error) { + signatureFileName := filepath.Base(signaturePath) + format := strings.Split(signatureFileName, ".")[1] + switch format { + case "cose": + return cose.MediaTypeEnvelope, nil + case "jws": + return jws.MediaTypeEnvelope, nil + } + return "", fmt.Errorf("unsupported signature format %s", format) +} + func signatureFilepath(signatureDirectory, blobPath, signatureFormat string) string { blobFilename := filepath.Base(blobPath) signatureFilename := fmt.Sprintf("%s.%s.sig", blobFilename, signatureFormat) From 94b586a7e46948bd3539cfba05330dc5c3e2d924 Mon Sep 17 00:00:00 2001 From: Patrick Zheng Date: Thu, 9 Jan 2025 16:49:03 +0800 Subject: [PATCH 24/53] fix tests Signed-off-by: Patrick Zheng --- test/e2e/go.mod | 1 + test/e2e/go.sum | 2 ++ 2 files changed, 3 insertions(+) diff --git a/test/e2e/go.mod b/test/e2e/go.mod index 0829a1938..c493de62b 100644 --- a/test/e2e/go.mod +++ b/test/e2e/go.mod @@ -15,6 +15,7 @@ require ( github.com/fxamacker/cbor/v2 v2.7.0 // indirect github.com/go-logr/logr v1.4.2 // indirect github.com/go-task/slim-sprig/v3 v3.0.0 // indirect + github.com/golang-jwt/jwt/v4 v4.5.1 // indirect github.com/google/go-cmp v0.6.0 // indirect github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad // indirect github.com/notaryproject/tspclient-go v1.0.0-rc.1 // indirect diff --git a/test/e2e/go.sum b/test/e2e/go.sum index ce903a174..f6e199022 100644 --- a/test/e2e/go.sum +++ b/test/e2e/go.sum @@ -6,6 +6,8 @@ github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= +github.com/golang-jwt/jwt/v4 v4.5.1 h1:JdqV9zKUdtaa9gdPlywC3aeoEsR681PlKC+4F5gQgeo= +github.com/golang-jwt/jwt/v4 v4.5.1/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad h1:a6HEuzUHeKH6hwfN/ZoQgRgVIWFJljSWa/zetS2WTvg= From ce63a532929b6c72bfa33d975232fcd27d3d0450 Mon Sep 17 00:00:00 2001 From: Patrick Zheng Date: Thu, 9 Jan 2025 16:52:46 +0800 Subject: [PATCH 25/53] fix tests Signed-off-by: Patrick Zheng --- test/e2e/go.mod | 1 - test/e2e/go.sum | 2 -- test/e2e/suite/command/blob/verify.go | 21 +-------------------- 3 files changed, 1 insertion(+), 23 deletions(-) diff --git a/test/e2e/go.mod b/test/e2e/go.mod index c493de62b..0829a1938 100644 --- a/test/e2e/go.mod +++ b/test/e2e/go.mod @@ -15,7 +15,6 @@ require ( github.com/fxamacker/cbor/v2 v2.7.0 // indirect github.com/go-logr/logr v1.4.2 // indirect github.com/go-task/slim-sprig/v3 v3.0.0 // indirect - github.com/golang-jwt/jwt/v4 v4.5.1 // indirect github.com/google/go-cmp v0.6.0 // indirect github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad // indirect github.com/notaryproject/tspclient-go v1.0.0-rc.1 // indirect diff --git a/test/e2e/go.sum b/test/e2e/go.sum index f6e199022..ce903a174 100644 --- a/test/e2e/go.sum +++ b/test/e2e/go.sum @@ -6,8 +6,6 @@ github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= -github.com/golang-jwt/jwt/v4 v4.5.1 h1:JdqV9zKUdtaa9gdPlywC3aeoEsR681PlKC+4F5gQgeo= -github.com/golang-jwt/jwt/v4 v4.5.1/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad h1:a6HEuzUHeKH6hwfN/ZoQgRgVIWFJljSWa/zetS2WTvg= diff --git a/test/e2e/suite/command/blob/verify.go b/test/e2e/suite/command/blob/verify.go index c6c47fb20..e76ee5f81 100644 --- a/test/e2e/suite/command/blob/verify.go +++ b/test/e2e/suite/command/blob/verify.go @@ -16,10 +16,7 @@ package blob import ( "fmt" "path/filepath" - "strings" - "github.com/notaryproject/notation-core-go/signature/cose" - "github.com/notaryproject/notation-core-go/signature/jws" . "github.com/notaryproject/notation/test/e2e/internal/notation" "github.com/notaryproject/notation/test/e2e/internal/utils" . "github.com/notaryproject/notation/test/e2e/suite/common" @@ -36,28 +33,12 @@ var _ = Describe("notation blob verify", func() { MatchKeyWords("Signature file written to") signaturePath := signatureFilepath(blobDir, blobPath, "jws") - signatureMediaType, err := parseSignatureMediaType(signaturePath) - if err != nil { - Fail(err.Error()) - } - notation.Exec("blob", "verify", "--signature", signatureMediaType, blobPath). + notation.Exec("blob", "verify", "--signature", signaturePath, blobPath). MatchKeyWords(VerifySuccessfully) }) }) }) -func parseSignatureMediaType(signaturePath string) (string, error) { - signatureFileName := filepath.Base(signaturePath) - format := strings.Split(signatureFileName, ".")[1] - switch format { - case "cose": - return cose.MediaTypeEnvelope, nil - case "jws": - return jws.MediaTypeEnvelope, nil - } - return "", fmt.Errorf("unsupported signature format %s", format) -} - func signatureFilepath(signatureDirectory, blobPath, signatureFormat string) string { blobFilename := filepath.Base(blobPath) signatureFilename := fmt.Sprintf("%s.%s.sig", blobFilename, signatureFormat) From 3602591610bf9267e0bcd1865c1c109b48c664b8 Mon Sep 17 00:00:00 2001 From: Patrick Zheng Date: Fri, 10 Jan 2025 13:07:37 +0800 Subject: [PATCH 26/53] update Signed-off-by: Patrick Zheng --- test/e2e/suite/command/blob/verify.go | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/test/e2e/suite/command/blob/verify.go b/test/e2e/suite/command/blob/verify.go index e76ee5f81..5fd02ea9f 100644 --- a/test/e2e/suite/command/blob/verify.go +++ b/test/e2e/suite/command/blob/verify.go @@ -15,6 +15,7 @@ package blob import ( "fmt" + "os" "path/filepath" . "github.com/notaryproject/notation/test/e2e/internal/notation" @@ -37,6 +38,31 @@ var _ = Describe("notation blob verify", func() { MatchKeyWords(VerifySuccessfully) }) }) + + // Failure cases + It("with blob verify no permission to read blob", func() { + HostWithBlob(BaseBlobOptions(), func(notation *utils.ExecOpts, blobPath string, vhost *utils.VirtualHost) { + noPermissionBlobPath := filepath.Join(vhost.AbsolutePath(), "noPermissionBlob") + newBlobFile, err := os.Create(noPermissionBlobPath) + if err != nil { + Fail(err.Error()) + } + defer newBlobFile.Close() + + blobDir := filepath.Dir(noPermissionBlobPath) + notation.Exec("blob", "sign", "--force", "--signature-directory", blobDir, blobPath). + MatchKeyWords(SignSuccessfully). + MatchKeyWords("Signature file written to") + if err := os.Chmod(noPermissionBlobPath, 0000); err != nil { + Fail(err.Error()) + } + defer os.Chmod(noPermissionBlobPath, 0700) + + signaturePath := signatureFilepath(blobDir, blobPath, "jws") + notation.ExpectFailure().Exec("blob", "verify", "--signature", signaturePath, blobPath). + MatchErrKeyWords("permission denied") + }) + }) }) func signatureFilepath(signatureDirectory, blobPath, signatureFormat string) string { From eeaf02ceb5ba0cfc726bd4d2ff8acf363d9af07b Mon Sep 17 00:00:00 2001 From: Patrick Zheng Date: Fri, 10 Jan 2025 15:48:52 +0800 Subject: [PATCH 27/53] added e2e tests Signed-off-by: Patrick Zheng --- cmd/notation/blob/verify.go | 1 + test/e2e/suite/command/blob/verify.go | 88 ++++++++++++++++--- .../blob/trustpolicies/trustpolicy.blob.json | 7 +- 3 files changed, 83 insertions(+), 13 deletions(-) diff --git a/cmd/notation/blob/verify.go b/cmd/notation/blob/verify.go index e39834301..da9b3fc88 100644 --- a/cmd/notation/blob/verify.go +++ b/cmd/notation/blob/verify.go @@ -82,6 +82,7 @@ Example - Verify the signature on a blob artifact using a policy statement name: return runVerify(cmd, opts) }, } + opts.LoggingFlagOpts.ApplyFlags(command.Flags()) command.Flags().StringVar(&opts.signaturePath, "signature", "", "filepath of the signature to be verified") command.Flags().StringArrayVar(&opts.pluginConfig, "plugin-config", nil, "{key}={value} pairs that are passed as it is to a plugin, if the verification is associated with a verification plugin, refer plugin documentation to set appropriate values") command.Flags().StringVar(&opts.blobMediaType, "media-type", "", "media type of the blob to verify") diff --git a/test/e2e/suite/command/blob/verify.go b/test/e2e/suite/command/blob/verify.go index 5fd02ea9f..f42d15bd9 100644 --- a/test/e2e/suite/command/blob/verify.go +++ b/test/e2e/suite/command/blob/verify.go @@ -28,29 +28,97 @@ var _ = Describe("notation blob verify", func() { // Success cases It("with blob verify", func() { HostWithBlob(BaseBlobOptions(), func(notation *utils.ExecOpts, blobPath string, vhost *utils.VirtualHost) { - blobDir := filepath.Dir(blobPath) - notation.Exec("blob", "sign", "--force", "--signature-directory", blobDir, blobPath). + workDir := vhost.AbsolutePath() + notation.WithWorkDir(workDir).Exec("blob", "sign", blobPath). MatchKeyWords(SignSuccessfully). MatchKeyWords("Signature file written to") - signaturePath := signatureFilepath(blobDir, blobPath, "jws") - notation.Exec("blob", "verify", "--signature", signaturePath, blobPath). + signaturePath := signatureFilepath(workDir, blobPath, "jws") + notation.Exec("blob", "verify", "-d", "--signature", signaturePath, blobPath). + MatchKeyWords(VerifySuccessfully). + // debug log message outputs to stderr + MatchErrKeyWords( + "Verify signature of media type application/jose+json", + "Name: test-blob-global-statement", + ) + }) + }) + + It("with COSE signature", func() { + HostWithBlob(BaseBlobOptions(), func(notation *utils.ExecOpts, blobPath string, vhost *utils.VirtualHost) { + workDir := vhost.AbsolutePath() + notation.WithWorkDir(workDir).Exec("blob", "--signature-format", "cose", "sign", blobPath). + MatchKeyWords(SignSuccessfully). + MatchKeyWords("Signature file written to") + + signaturePath := signatureFilepath(workDir, blobPath, "cose") + notation.Exec("blob", "verify", "-d", "--signature", signaturePath, blobPath). + MatchKeyWords(VerifySuccessfully). + // debug log message outputs to stderr + MatchErrKeyWords( + "Verify signature of media type application/cose", + ) + }) + }) + + It("with policy name", func() { + HostWithBlob(BaseBlobOptions(), func(notation *utils.ExecOpts, blobPath string, vhost *utils.VirtualHost) { + workDir := vhost.AbsolutePath() + notation.WithWorkDir(workDir).Exec("blob", "sign", blobPath). + MatchKeyWords(SignSuccessfully). + MatchKeyWords("Signature file written to") + + signaturePath := signatureFilepath(workDir, blobPath, "jws") + notation.Exec("blob", "verify", "-d", "--policy-name", "test-blob-statement", "--signature", signaturePath, blobPath). + MatchKeyWords(VerifySuccessfully). + // debug log message outputs to stderr + MatchErrKeyWords( + "Name: test-blob-statement", + ) + }) + }) + + It("with media type", func() { + HostWithBlob(BaseBlobOptions(), func(notation *utils.ExecOpts, blobPath string, vhost *utils.VirtualHost) { + workDir := vhost.AbsolutePath() + notation.WithWorkDir(workDir).Exec("blob", "sign", "--media-type", "image/jpeg", blobPath). + MatchKeyWords(SignSuccessfully). + MatchKeyWords("Signature file written to") + + signaturePath := signatureFilepath(workDir, blobPath, "jws") + notation.Exec("blob", "verify", "-d", "--media-type", "image/jpeg", "--signature", signaturePath, blobPath). MatchKeyWords(VerifySuccessfully) }) }) + It("with timestamping", func() { + HostWithBlob(BaseBlobOptions(), func(notation *utils.ExecOpts, blobPath string, vhost *utils.VirtualHost) { + workDir := vhost.AbsolutePath() + notation.WithWorkDir(workDir).Exec("blob", "sign", "--timestamp-url", tsaURL, "--timestamp-root-cert", filepath.Join(NotationE2EConfigPath, "timestamp", "DigiCertTSARootSHA384.cer"), blobPath, "-d"). + MatchKeyWords(SignSuccessfully). + MatchKeyWords("Signature file written to") + + signaturePath := signatureFilepath(workDir, blobPath, "jws") + notation.Exec("blob", "verify", "-d", "--signature", signaturePath, blobPath). + MatchKeyWords(VerifySuccessfully). + MatchErrKeyWords( + "Timestamp verification: Success", + ) + }) + }) + // Failure cases - It("with blob verify no permission to read blob", func() { + It("with no permission to read blob", func() { HostWithBlob(BaseBlobOptions(), func(notation *utils.ExecOpts, blobPath string, vhost *utils.VirtualHost) { - noPermissionBlobPath := filepath.Join(vhost.AbsolutePath(), "noPermissionBlob") + workDir := vhost.AbsolutePath() + noPermissionBlobPath := filepath.Join(workDir, "noPermissionBlob") newBlobFile, err := os.Create(noPermissionBlobPath) if err != nil { Fail(err.Error()) } defer newBlobFile.Close() - blobDir := filepath.Dir(noPermissionBlobPath) - notation.Exec("blob", "sign", "--force", "--signature-directory", blobDir, blobPath). + notation.Exec("blob", "sign", "--force", "--signature-directory", workDir, noPermissionBlobPath). MatchKeyWords(SignSuccessfully). MatchKeyWords("Signature file written to") if err := os.Chmod(noPermissionBlobPath, 0000); err != nil { @@ -58,8 +126,8 @@ var _ = Describe("notation blob verify", func() { } defer os.Chmod(noPermissionBlobPath, 0700) - signaturePath := signatureFilepath(blobDir, blobPath, "jws") - notation.ExpectFailure().Exec("blob", "verify", "--signature", signaturePath, blobPath). + signaturePath := signatureFilepath(workDir, noPermissionBlobPath, "jws") + notation.ExpectFailure().Exec("blob", "verify", "--signature", signaturePath, noPermissionBlobPath). MatchErrKeyWords("permission denied") }) }) diff --git a/test/e2e/testdata/blob/trustpolicies/trustpolicy.blob.json b/test/e2e/testdata/blob/trustpolicies/trustpolicy.blob.json index 85429cfb4..e2143fbe2 100644 --- a/test/e2e/testdata/blob/trustpolicies/trustpolicy.blob.json +++ b/test/e2e/testdata/blob/trustpolicies/trustpolicy.blob.json @@ -14,11 +14,12 @@ { "name": "test-blob-global-statement", "signatureVerification": { - "level" : "audit" + "level" : "strict", + "verifyTimestamp": "always" }, - "trustStores": [ "ca:e2e" ], + "trustStores": [ "ca:e2e", "tsa:e2e" ], "trustedIdentities": [ - "*" + "*" ], "globalPolicy": true } From 8cc2aebea9303a00b1ac851881af81defb7a86c4 Mon Sep 17 00:00:00 2001 From: Patrick Zheng Date: Fri, 10 Jan 2025 16:09:21 +0800 Subject: [PATCH 28/53] fix e2e tests Signed-off-by: Patrick Zheng --- test/e2e/suite/command/blob/verify.go | 2 +- .../blob/trustpolicies/trustpolicy.blob.json | 13 ++++++++++++- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/test/e2e/suite/command/blob/verify.go b/test/e2e/suite/command/blob/verify.go index f42d15bd9..9e238ee25 100644 --- a/test/e2e/suite/command/blob/verify.go +++ b/test/e2e/suite/command/blob/verify.go @@ -99,7 +99,7 @@ var _ = Describe("notation blob verify", func() { MatchKeyWords("Signature file written to") signaturePath := signatureFilepath(workDir, blobPath, "jws") - notation.Exec("blob", "verify", "-d", "--signature", signaturePath, blobPath). + notation.Exec("blob", "verify", "-d", "--policy-name", "test-blob-with-timestamping", "--signature", signaturePath, blobPath). MatchKeyWords(VerifySuccessfully). MatchErrKeyWords( "Timestamp verification: Success", diff --git a/test/e2e/testdata/blob/trustpolicies/trustpolicy.blob.json b/test/e2e/testdata/blob/trustpolicies/trustpolicy.blob.json index e2143fbe2..a149f26ac 100644 --- a/test/e2e/testdata/blob/trustpolicies/trustpolicy.blob.json +++ b/test/e2e/testdata/blob/trustpolicies/trustpolicy.blob.json @@ -22,6 +22,17 @@ "*" ], "globalPolicy": true - } + }, + { + "name": "test-blob-with-timestamping", + "signatureVerification": { + "level" : "strict", + "verifyTimestamp": "always" + }, + "trustStores": [ "ca:e2e", "tsa:e2e" ], + "trustedIdentities": [ + "*" + ] + } ] } From 32b009b4815cee7a37ee01772298c66aeb3d4369 Mon Sep 17 00:00:00 2001 From: Patrick Zheng Date: Fri, 10 Jan 2025 16:14:38 +0800 Subject: [PATCH 29/53] fix e2e tests Signed-off-by: Patrick Zheng --- test/e2e/testdata/blob/trustpolicies/trustpolicy.blob.json | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/test/e2e/testdata/blob/trustpolicies/trustpolicy.blob.json b/test/e2e/testdata/blob/trustpolicies/trustpolicy.blob.json index a149f26ac..9b90a704a 100644 --- a/test/e2e/testdata/blob/trustpolicies/trustpolicy.blob.json +++ b/test/e2e/testdata/blob/trustpolicies/trustpolicy.blob.json @@ -14,10 +14,9 @@ { "name": "test-blob-global-statement", "signatureVerification": { - "level" : "strict", - "verifyTimestamp": "always" + "level" : "strict" }, - "trustStores": [ "ca:e2e", "tsa:e2e" ], + "trustStores": [ "ca:e2e" ], "trustedIdentities": [ "*" ], From 54ecf6ee5115ef8366b4c507493638fa9a98634b Mon Sep 17 00:00:00 2001 From: Patrick Zheng Date: Fri, 10 Jan 2025 16:22:53 +0800 Subject: [PATCH 30/53] fix e2e tests Signed-off-by: Patrick Zheng --- test/e2e/internal/notation/host.go | 2 ++ test/e2e/suite/command/blob/verify.go | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/test/e2e/internal/notation/host.go b/test/e2e/internal/notation/host.go index f91672bbb..1dbf0d702 100644 --- a/test/e2e/internal/notation/host.go +++ b/test/e2e/internal/notation/host.go @@ -158,6 +158,8 @@ func BaseBlobOptions() []utils.HostOption { AddKeyOption(filepath.Join(NotationE2ELocalKeysDir, "e2e.key"), filepath.Join(NotationE2ELocalKeysDir, "e2e.crt")), AddTrustStoreOption("e2e", filepath.Join(NotationE2ELocalKeysDir, "e2e.crt")), AddTrustPolicyOption("trustpolicy.blob.json", true), + AddTimestampTrustStoreOption("e2e", filepath.Join(NotationE2EConfigPath, "timestamp", "globalsignTSARoot.cer")), + AddTimestampTrustStoreOption("e2e", filepath.Join(NotationE2EConfigPath, "timestamp", "DigiCertTSARootSHA384.cer")), ) } diff --git a/test/e2e/suite/command/blob/verify.go b/test/e2e/suite/command/blob/verify.go index 9e238ee25..6ebf81498 100644 --- a/test/e2e/suite/command/blob/verify.go +++ b/test/e2e/suite/command/blob/verify.go @@ -39,7 +39,7 @@ var _ = Describe("notation blob verify", func() { // debug log message outputs to stderr MatchErrKeyWords( "Verify signature of media type application/jose+json", - "Name: test-blob-global-statement", + "Name:test-blob-global-statement", ) }) }) @@ -73,7 +73,7 @@ var _ = Describe("notation blob verify", func() { MatchKeyWords(VerifySuccessfully). // debug log message outputs to stderr MatchErrKeyWords( - "Name: test-blob-statement", + "Name:test-blob-statement", ) }) }) From fa50e180e6f5f0d5929f5cc1dc6febbc5e258ddd Mon Sep 17 00:00:00 2001 From: Patrick Zheng Date: Fri, 10 Jan 2025 16:50:50 +0800 Subject: [PATCH 31/53] add more e2e tests Signed-off-by: Patrick Zheng --- test/e2e/suite/command/blob/verify.go | 76 ++++++++++++++++++++++++++- 1 file changed, 75 insertions(+), 1 deletion(-) diff --git a/test/e2e/suite/command/blob/verify.go b/test/e2e/suite/command/blob/verify.go index 6ebf81498..8af481007 100644 --- a/test/e2e/suite/command/blob/verify.go +++ b/test/e2e/suite/command/blob/verify.go @@ -101,6 +101,7 @@ var _ = Describe("notation blob verify", func() { signaturePath := signatureFilepath(workDir, blobPath, "jws") notation.Exec("blob", "verify", "-d", "--policy-name", "test-blob-with-timestamping", "--signature", signaturePath, blobPath). MatchKeyWords(VerifySuccessfully). + // debug log message outputs to stderr MatchErrKeyWords( "Timestamp verification: Success", ) @@ -118,7 +119,7 @@ var _ = Describe("notation blob verify", func() { } defer newBlobFile.Close() - notation.Exec("blob", "sign", "--force", "--signature-directory", workDir, noPermissionBlobPath). + notation.WithWorkDir(workDir).Exec("blob", "sign", noPermissionBlobPath). MatchKeyWords(SignSuccessfully). MatchKeyWords("Signature file written to") if err := os.Chmod(noPermissionBlobPath, 0000); err != nil { @@ -131,6 +132,79 @@ var _ = Describe("notation blob verify", func() { MatchErrKeyWords("permission denied") }) }) + + It("with no permission to read signature file", func() { + HostWithBlob(BaseBlobOptions(), func(notation *utils.ExecOpts, blobPath string, vhost *utils.VirtualHost) { + workDir := vhost.AbsolutePath() + notation.WithWorkDir(workDir).Exec("blob", "sign", blobPath). + MatchKeyWords(SignSuccessfully). + MatchKeyWords("Signature file written to") + noPermissionSignaturePath := signatureFilepath(workDir, blobPath, "jws") + if err := os.Chmod(noPermissionSignaturePath, 0000); err != nil { + Fail(err.Error()) + } + defer os.Chmod(noPermissionSignaturePath, 0700) + + notation.ExpectFailure().Exec("blob", "verify", "--signature", noPermissionSignaturePath, blobPath). + MatchErrKeyWords("permission denied") + }) + }) + + It("with invalid plugin-config", func() { + HostWithBlob(BaseOptions(), func(notation *utils.ExecOpts, blobPath string, vhost *utils.VirtualHost) { + workDir := vhost.AbsolutePath() + notation.WithWorkDir(workDir).Exec("blob", "sign", blobPath). + MatchKeyWords(SignSuccessfully). + MatchKeyWords("Signature file written to") + + signaturePath := signatureFilepath(workDir, blobPath, "jws") + notation.ExpectFailure().Exec("blob", "verify", "--plugin-config", "invalid", "--signature", signaturePath, blobPath). + MatchErrKeyWords(`could not parse flag plugin-config: key-value pair requires "=" as separator`) + }) + }) + + It("with invalid user metadata", func() { + HostWithBlob(BaseOptions(), func(notation *utils.ExecOpts, blobPath string, vhost *utils.VirtualHost) { + workDir := vhost.AbsolutePath() + notation.WithWorkDir(workDir).Exec("blob", "sign", blobPath). + MatchKeyWords(SignSuccessfully). + MatchKeyWords("Signature file written to") + + signaturePath := signatureFilepath(workDir, blobPath, "jws") + notation.ExpectFailure().Exec("blob", "verify", "--user-metadata", "invalid", "--signature", signaturePath, blobPath). + MatchErrKeyWords(`could not parse flag user-metadata: key-value pair requires "=" as separator`) + }) + }) + + It("with invalid signature format", func() { + HostWithBlob(BaseBlobOptions(), func(notation *utils.ExecOpts, blobPath string, vhost *utils.VirtualHost) { + workDir := vhost.AbsolutePath() + notation.WithWorkDir(workDir).Exec("blob", "sign", blobPath). + MatchKeyWords(SignSuccessfully). + MatchKeyWords("Signature file written to") + + signaturePath := signatureFilepath(workDir, blobPath, "jws") + invalidSignaturePath := signatureFilepath(workDir, blobPath, "invalid") + if err := os.Rename(signaturePath, invalidSignaturePath); err != nil { + Fail(err.Error()) + } + notation.ExpectFailure().Exec("blob", "verify", "--signature", invalidSignaturePath, blobPath). + MatchErrKeyWords("unsupported signature format invalid") + }) + }) + + It("with mismatch media type", func() { + HostWithBlob(BaseBlobOptions(), func(notation *utils.ExecOpts, blobPath string, vhost *utils.VirtualHost) { + workDir := vhost.AbsolutePath() + notation.WithWorkDir(workDir).Exec("blob", "sign", blobPath). + MatchKeyWords(SignSuccessfully). + MatchKeyWords("Signature file written to") + + signaturePath := signatureFilepath(workDir, blobPath, "jws") + notation.ExpectFailure().Exec("blob", "verify", "--media-type", "image/jpeg", "--signature", signaturePath, blobPath). + MatchErrKeyWords("integrity check failed. signature does not match the given blob") + }) + }) }) func signatureFilepath(signatureDirectory, blobPath, signatureFormat string) string { From 07c66b444f1f85a9f03ecdec6353b7c4f6f21f42 Mon Sep 17 00:00:00 2001 From: Patrick Zheng Date: Fri, 10 Jan 2025 16:56:04 +0800 Subject: [PATCH 32/53] add more e2e tests Signed-off-by: Patrick Zheng --- test/e2e/suite/command/blob/verify.go | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/test/e2e/suite/command/blob/verify.go b/test/e2e/suite/command/blob/verify.go index 8af481007..9d8c90512 100644 --- a/test/e2e/suite/command/blob/verify.go +++ b/test/e2e/suite/command/blob/verify.go @@ -151,7 +151,7 @@ var _ = Describe("notation blob verify", func() { }) It("with invalid plugin-config", func() { - HostWithBlob(BaseOptions(), func(notation *utils.ExecOpts, blobPath string, vhost *utils.VirtualHost) { + HostWithBlob(BaseBlobOptions(), func(notation *utils.ExecOpts, blobPath string, vhost *utils.VirtualHost) { workDir := vhost.AbsolutePath() notation.WithWorkDir(workDir).Exec("blob", "sign", blobPath). MatchKeyWords(SignSuccessfully). @@ -164,7 +164,7 @@ var _ = Describe("notation blob verify", func() { }) It("with invalid user metadata", func() { - HostWithBlob(BaseOptions(), func(notation *utils.ExecOpts, blobPath string, vhost *utils.VirtualHost) { + HostWithBlob(BaseBlobOptions(), func(notation *utils.ExecOpts, blobPath string, vhost *utils.VirtualHost) { workDir := vhost.AbsolutePath() notation.WithWorkDir(workDir).Exec("blob", "sign", blobPath). MatchKeyWords(SignSuccessfully). @@ -205,6 +205,19 @@ var _ = Describe("notation blob verify", func() { MatchErrKeyWords("integrity check failed. signature does not match the given blob") }) }) + + It("with no trust policy", func() { + HostWithBlob(BaseOptions(), func(notation *utils.ExecOpts, blobPath string, vhost *utils.VirtualHost) { + workDir := vhost.AbsolutePath() + notation.WithWorkDir(workDir).Exec("blob", "sign", blobPath). + MatchKeyWords(SignSuccessfully). + MatchKeyWords("Signature file written to") + + signaturePath := signatureFilepath(workDir, blobPath, "jws") + notation.ExpectFailure().Exec("blob", "verify", "--signature", signaturePath, blobPath). + MatchErrKeyWords(`trust policy is not present. To create a trust policy, see: https://notaryproject.dev/docs/quickstart/#create-a-trust-policy`) + }) + }) }) func signatureFilepath(signatureDirectory, blobPath, signatureFormat string) string { From 4c070987d24b5b8e752bade7580caf6017676bcb Mon Sep 17 00:00:00 2001 From: Patrick Zheng Date: Fri, 10 Jan 2025 17:12:28 +0800 Subject: [PATCH 33/53] added more e2e tests Signed-off-by: Patrick Zheng --- cmd/notation/blob/verify.go | 2 +- specs/commandline/blob.md | 14 +++++++------- test/e2e/suite/command/blob/verify.go | 27 ++++++++++++++++++++++++++- 3 files changed, 34 insertions(+), 9 deletions(-) diff --git a/cmd/notation/blob/verify.go b/cmd/notation/blob/verify.go index da9b3fc88..8509a76c1 100644 --- a/cmd/notation/blob/verify.go +++ b/cmd/notation/blob/verify.go @@ -60,7 +60,7 @@ Example - Verify the signature on a blob artifact using a policy statement name: ` command := &cobra.Command{ Use: "verify [flags] --signature ", - Short: "verify a signature associated with a blob", + Short: "Verify a signature associated with a blob", Long: longMessage, Args: func(cmd *cobra.Command, args []string) error { if len(args) == 0 { diff --git a/specs/commandline/blob.md b/specs/commandline/blob.md index c9f9e0810..a71c5ac4e 100644 --- a/specs/commandline/blob.md +++ b/specs/commandline/blob.md @@ -160,14 +160,14 @@ Usage: notation blob verify [flags] --signature Flags: - --signature string filepath of the signature to be verified - --media-type string optional media type of the blob to verify - --policy-name string optional policy name to verify against. If not provided, the global policy is used if exists - -m, --user-metadata stringArray user defined {key}={value} pairs that must be present in the signature for successful verification if provided + -d, --debug debug mode + -h, --help help for verify + --media-type string media type of the blob to verify --plugin-config stringArray {key}={value} pairs that are passed as it is to a plugin, if the verification is associated with a verification plugin, refer plugin documentation to set appropriate values - -d, --debug debug mode - -v, --verbose verbose mode - -h, --help help for inspect + --policy-name string policy name to verify against. If not provided, the global policy is used if exists + --signature string filepath of the signature to be verified + -m, --user-metadata stringArray user defined {key}={value} pairs that must be present in the signature for successful verification if provided + -v, --verbose verbose mode ``` ## Usage diff --git a/test/e2e/suite/command/blob/verify.go b/test/e2e/suite/command/blob/verify.go index 9d8c90512..c18093716 100644 --- a/test/e2e/suite/command/blob/verify.go +++ b/test/e2e/suite/command/blob/verify.go @@ -86,7 +86,7 @@ var _ = Describe("notation blob verify", func() { MatchKeyWords("Signature file written to") signaturePath := signatureFilepath(workDir, blobPath, "jws") - notation.Exec("blob", "verify", "-d", "--media-type", "image/jpeg", "--signature", signaturePath, blobPath). + notation.Exec("blob", "verify", "--media-type", "image/jpeg", "--signature", signaturePath, blobPath). MatchKeyWords(VerifySuccessfully) }) }) @@ -108,7 +108,32 @@ var _ = Describe("notation blob verify", func() { }) }) + It("with user metadata", func() { + HostWithBlob(BaseBlobOptions(), func(notation *utils.ExecOpts, blobPath string, vhost *utils.VirtualHost) { + workDir := vhost.AbsolutePath() + notation.WithWorkDir(workDir).Exec("blob", "sign", "--user-metadata", "k1=v1", blobPath). + MatchKeyWords(SignSuccessfully). + MatchKeyWords("Signature file written to") + + signaturePath := signatureFilepath(workDir, blobPath, "jws") + notation.Exec("blob", "verify", "--user-metadata", "k1=v1", "--signature", signaturePath, blobPath). + MatchKeyWords(VerifySuccessfully) + }) + }) + // Failure cases + It("with missing --signature flag", func() { + HostWithBlob(BaseBlobOptions(), func(notation *utils.ExecOpts, blobPath string, vhost *utils.VirtualHost) { + workDir := vhost.AbsolutePath() + notation.WithWorkDir(workDir).Exec("blob", "sign", blobPath). + MatchKeyWords(SignSuccessfully). + MatchKeyWords("Signature file written to") + + notation.ExpectFailure().Exec("blob", "verify", blobPath). + MatchErrKeyWords("filepath of the signature cannot be empty") + }) + }) + It("with no permission to read blob", func() { HostWithBlob(BaseBlobOptions(), func(notation *utils.ExecOpts, blobPath string, vhost *utils.VirtualHost) { workDir := vhost.AbsolutePath() From 1294938b19b1e3a6a5cd1ca11191c3aa1ff5c833 Mon Sep 17 00:00:00 2001 From: Patrick Zheng Date: Tue, 14 Jan 2025 14:15:11 +0800 Subject: [PATCH 34/53] update Signed-off-by: Patrick Zheng --- internal/ioutil/print_test.go | 29 +++++++++++++++++++++++++++++ specs/commandline/blob.md | 12 ++++++------ 2 files changed, 35 insertions(+), 6 deletions(-) create mode 100644 internal/ioutil/print_test.go diff --git a/internal/ioutil/print_test.go b/internal/ioutil/print_test.go new file mode 100644 index 000000000..02c976ce5 --- /dev/null +++ b/internal/ioutil/print_test.go @@ -0,0 +1,29 @@ +// Copyright The Notary Project Authors. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package ioutil + +import ( + "testing" + + "github.com/notaryproject/notation-go" +) + +func TestBlobVerificateFailure(t *testing.T) { + var outcomes []*notation.VerificationOutcome + expectedErrMsg := "provided signature verification failed against blob myblob" + err := PrintVerificationFailure(outcomes, "myblob", nil, true) + if err == nil || err.Error() != expectedErrMsg { + t.Fatalf("expected %s, but got %s", expectedErrMsg, err) + } +} diff --git a/specs/commandline/blob.md b/specs/commandline/blob.md index a71c5ac4e..6d683b287 100644 --- a/specs/commandline/blob.md +++ b/specs/commandline/blob.md @@ -6,11 +6,11 @@ Use `notation blob` command to sign, verify, and inspect signatures associated w Users can use `notation blob policy` command to manage trust policies for verifying a blob signature. The `notation blob policy` command provides a user-friendly way to manage trust policies for signed blobs. It allows users to show trust policy configuration, import/export a trust policy configuration file from/to a JSON file. For more details, see [blob trust policy specification and examples](https://github.com/notaryproject/specifications/blob/main/specs/trust-store-trust-policy.md#blob-trust-policy). -The sample trust policy file (`trustpolicy.blob.json`) for verifying signed blobs is shown below. This sample trust policy file, contains three different statements for different usecases: +The sample trust policy file (`trustpolicy.blob.json`) for verifying signed blobs is shown below. This sample trust policy file, contains three different statements for different use cases: -- The Policy named "wabbit-networks-policy" is for verifying blob artifacts signed by Wabbit Networks. -- Policy named "skip-verification-policy" is for skipping verification on blob artifacts. -- Policy "global-verification-policy" is for auditing verification results when user does not provide `--policy-name` argument in `notation blob verify` command. +- The policy named "wabbit-networks-policy" is for verifying blob artifacts signed by Wabbit Networks. +- The policy named "skip-verification-policy" is for skipping verification on blob artifacts. +- The policy "global-verification-policy" is for auditing verification results when user does not set the `--policy-name` flag in `notation blob verify` command. ```jsonc { @@ -380,8 +380,8 @@ The steps to update blob trust policy configuration: ## Verify blob signatures The `notation blob verify` command can be used to verify blob signatures. In order to verify signatures, user will need to setup a trust policy file `trustpolicy.blob.json` with policies for blobs. Below are two examples of how a policy configuration file can be setup for verifying blob signatures. -- The Policy named "wabbit-networks-policy" is for verifying blob artifacts signed by Wabbit Networks. -- Policy named "global-verification-policy" is for auditing verification results when user doesn't not provide `--policy-name` argument in `notation blob verify` command. +- The policy named "wabbit-networks-policy" is for verifying blob artifacts signed by Wabbit Networks. +- The policy named "global-verification-policy" is for auditing verification results when user does not set the `--policy-name` flag in `notation blob verify` command. ```jsonc { From d998243409e9e6d6af09362576a0131a648c3cf5 Mon Sep 17 00:00:00 2001 From: Patrick Zheng Date: Tue, 11 Feb 2025 16:49:45 +0800 Subject: [PATCH 35/53] resolved conflicts Signed-off-by: Patrick Zheng --- internal/ioutil/print.go | 3 --- 1 file changed, 3 deletions(-) diff --git a/internal/ioutil/print.go b/internal/ioutil/print.go index 146e70941..4dee44b5e 100644 --- a/internal/ioutil/print.go +++ b/internal/ioutil/print.go @@ -14,11 +14,8 @@ package ioutil import ( -<<<<<<< HEAD "encoding/json" "errors" -======= ->>>>>>> 73b15517d834b04e1f61aa60f6566093bc1b58c2 "fmt" "io" "io/fs" From b2973be6bf3a5c065550c4165cee17102a775e76 Mon Sep 17 00:00:00 2001 From: Patrick Zheng Date: Tue, 11 Feb 2025 17:00:45 +0800 Subject: [PATCH 36/53] resolve conflicts Signed-off-by: Patrick Zheng --- cmd/notation/blob/verify.go | 3 ++- cmd/notation/verify.go | 8 +++++--- internal/ioutil/print.go | 12 ++++++++++++ 3 files changed, 19 insertions(+), 4 deletions(-) diff --git a/cmd/notation/blob/verify.go b/cmd/notation/blob/verify.go index 8509a76c1..4b67e1615 100644 --- a/cmd/notation/blob/verify.go +++ b/cmd/notation/blob/verify.go @@ -151,7 +151,8 @@ func runVerify(command *cobra.Command, cmdOpts *blobVerifyOpts) error { // `application/jose+json` and `application/cose` are supported. func parseSignatureMediaType(signaturePath string) (string, error) { signatureFileName := filepath.Base(signaturePath) - format := strings.Split(signatureFileName, ".")[1] + sigFilenameArr := strings.Split(signatureFileName, ".") + format := sigFilenameArr[len(sigFilenameArr)-2] switch format { case "cose": return cose.MediaTypeEnvelope, nil diff --git a/cmd/notation/verify.go b/cmd/notation/verify.go index 199d33b8f..be434c824 100644 --- a/cmd/notation/verify.go +++ b/cmd/notation/verify.go @@ -14,23 +14,25 @@ package main import ( + "context" "errors" "fmt" + "io/fs" "os" + "github.com/notaryproject/notation-core-go/revocation/purpose" "github.com/notaryproject/notation-go" -<<<<<<< HEAD -======= "github.com/notaryproject/notation-go/dir" "github.com/notaryproject/notation-go/plugin" "github.com/notaryproject/notation-go/verifier" "github.com/notaryproject/notation-go/verifier/trustpolicy" "github.com/notaryproject/notation-go/verifier/truststore" "github.com/notaryproject/notation/cmd/notation/internal/display" ->>>>>>> 73b15517d834b04e1f61aa60f6566093bc1b58c2 "github.com/notaryproject/notation/cmd/notation/internal/experimental" "github.com/notaryproject/notation/cmd/notation/internal/option" "github.com/notaryproject/notation/internal/cmd" + "github.com/notaryproject/notation/internal/ioutil" + clirev "github.com/notaryproject/notation/internal/revocation" ocispec "github.com/opencontainers/image-spec/specs-go/v1" "github.com/spf13/cobra" ) diff --git a/internal/ioutil/print.go b/internal/ioutil/print.go index 4dee44b5e..9440af286 100644 --- a/internal/ioutil/print.go +++ b/internal/ioutil/print.go @@ -155,3 +155,15 @@ func PrintMetadataIfPresent(outcome *notation.VerificationOutcome) { PrintMetadataMap(os.Stdout, metadata) } } + +// PrintMetadataMap prints out metadata given the metatdata map +func PrintMetadataMap(w io.Writer, metadata map[string]string) error { + tw := newTabWriter(w) + fmt.Fprintln(tw, "\nKEY\tVALUE\t") + + for k, v := range metadata { + fmt.Fprintf(tw, "%v\t%v\t\n", k, v) + } + + return tw.Flush() +} From 31c15372341c5adde9de64a4aa7c20b82ca5ddfb Mon Sep 17 00:00:00 2001 From: Patrick Zheng Date: Tue, 11 Feb 2025 17:07:04 +0800 Subject: [PATCH 37/53] fix unit test Signed-off-by: Patrick Zheng --- cmd/notation/verify.go | 31 ------------------------------- internal/cmd/verifier_test.go | 2 +- 2 files changed, 1 insertion(+), 32 deletions(-) diff --git a/cmd/notation/verify.go b/cmd/notation/verify.go index be434c824..7a01478e3 100644 --- a/cmd/notation/verify.go +++ b/cmd/notation/verify.go @@ -14,25 +14,18 @@ package main import ( - "context" "errors" "fmt" "io/fs" "os" - "github.com/notaryproject/notation-core-go/revocation/purpose" "github.com/notaryproject/notation-go" - "github.com/notaryproject/notation-go/dir" - "github.com/notaryproject/notation-go/plugin" - "github.com/notaryproject/notation-go/verifier" - "github.com/notaryproject/notation-go/verifier/trustpolicy" "github.com/notaryproject/notation-go/verifier/truststore" "github.com/notaryproject/notation/cmd/notation/internal/display" "github.com/notaryproject/notation/cmd/notation/internal/experimental" "github.com/notaryproject/notation/cmd/notation/internal/option" "github.com/notaryproject/notation/internal/cmd" "github.com/notaryproject/notation/internal/ioutil" - clirev "github.com/notaryproject/notation/internal/revocation" ocispec "github.com/opencontainers/image-spec/specs-go/v1" "github.com/spf13/cobra" ) @@ -200,27 +193,3 @@ func checkVerificationFailure(outcomes []*notation.VerificationOutcome, printOut } return nil } - -func getVerifier(ctx context.Context) (notation.Verifier, error) { - // revocation check - revocationCodeSigningValidator, err := clirev.NewRevocationValidator(ctx, purpose.CodeSigning) - if err != nil { - return nil, err - } - revocationTimestampingValidator, err := clirev.NewRevocationValidator(ctx, purpose.Timestamping) - if err != nil { - return nil, err - } - - // trust policy and trust store - policyDocument, err := trustpolicy.LoadOCIDocument() - if err != nil { - return nil, err - } - x509TrustStore := truststore.NewX509TrustStore(dir.ConfigFS()) - - return verifier.NewVerifierWithOptions(policyDocument, nil, x509TrustStore, plugin.NewCLIManager(dir.PluginFS()), verifier.VerifierOptions{ - RevocationCodeSigningValidator: revocationCodeSigningValidator, - RevocationTimestampingValidator: revocationTimestampingValidator, - }) -} diff --git a/internal/cmd/verifier_test.go b/internal/cmd/verifier_test.go index 8a7e5bc82..15791382e 100644 --- a/internal/cmd/verifier_test.go +++ b/internal/cmd/verifier_test.go @@ -107,7 +107,7 @@ func TestGetVerifier(t *testing.T) { } t.Cleanup(func() { os.RemoveAll(tempRoot) }) - expectedErrMsg := "blob trust policy has empty version, version must be specified" + expectedErrMsg := "blob trust policy document has empty version, version must be specified" _, err := GetVerifier(context.Background(), true) if err == nil || err.Error() != expectedErrMsg { t.Fatalf("expected %s, but got %s", expectedErrMsg, err) From 13bfb9e370d94043decc0bc48b69bc70e880ddfc Mon Sep 17 00:00:00 2001 From: Patrick Zheng Date: Tue, 11 Feb 2025 18:02:47 +0800 Subject: [PATCH 38/53] blob verify Signed-off-by: Patrick Zheng --- cmd/notation/blob/verify.go | 9 ++- cmd/notation/internal/display/handler.go | 8 +- .../internal/display/metadata/interface.go | 13 ++++ .../internal/display/metadata/text/base.go | 76 +++++++++++++++++++ .../text/{verify_test.go => base_test.go} | 2 +- .../display/metadata/text/blobverify.go | 48 ++++++++++++ .../internal/display/metadata/text/verify.go | 58 +------------- cmd/notation/verify.go | 37 +-------- internal/ioutil/print.go | 48 ------------ 9 files changed, 155 insertions(+), 144 deletions(-) create mode 100644 cmd/notation/internal/display/metadata/text/base.go rename cmd/notation/internal/display/metadata/text/{verify_test.go => base_test.go} (97%) create mode 100644 cmd/notation/internal/display/metadata/text/blobverify.go diff --git a/cmd/notation/blob/verify.go b/cmd/notation/blob/verify.go index 4b67e1615..cdafa7e79 100644 --- a/cmd/notation/blob/verify.go +++ b/cmd/notation/blob/verify.go @@ -23,6 +23,8 @@ import ( "github.com/notaryproject/notation-core-go/signature/cose" "github.com/notaryproject/notation-core-go/signature/jws" "github.com/notaryproject/notation-go" + "github.com/notaryproject/notation/cmd/notation/internal/display" + "github.com/notaryproject/notation/cmd/notation/internal/option" "github.com/notaryproject/notation/internal/cmd" "github.com/notaryproject/notation/internal/ioutil" "github.com/spf13/cobra" @@ -30,6 +32,7 @@ import ( type blobVerifyOpts struct { cmd.LoggingFlagOpts + option.Common blobPath string signaturePath string pluginConfig []string @@ -76,6 +79,7 @@ Example - Verify the signature on a blob artifact using a policy statement name: if cmd.Flags().Changed("media-type") && opts.blobMediaType == "" { return errors.New("--media-type is set but with empty value") } + opts.Common.Parse(cmd) return nil }, RunE: func(cmd *cobra.Command, args []string) error { @@ -97,6 +101,7 @@ func runVerify(command *cobra.Command, cmdOpts *blobVerifyOpts) error { ctx := cmdOpts.LoggingFlagOpts.InitializeLogger(command.Context()) // initialize + displayHandler := display.NewBlobVerifyHandler(cmdOpts.Printer) blobFile, err := os.Open(cmdOpts.blobPath) if err != nil { return err @@ -143,8 +148,8 @@ func runVerify(command *cobra.Command, cmdOpts *blobVerifyOpts) error { if err != nil { return err } - ioutil.PrintVerificationSuccess(outcomes, cmdOpts.blobPath) - return nil + displayHandler.OnVerifySucceeded(outcomes, cmdOpts.blobPath) + return displayHandler.Render() } // parseSignatureMediaType returns the media type of the signature file. diff --git a/cmd/notation/internal/display/handler.go b/cmd/notation/internal/display/handler.go index da43ca4e4..f23ce96cb 100644 --- a/cmd/notation/internal/display/handler.go +++ b/cmd/notation/internal/display/handler.go @@ -43,7 +43,13 @@ func NewInpsectHandler(printer *output.Printer, format option.Format) (metadata. } // NewVerifyHandler creates a new metadata VerifyHandler for printing -// veriifcation result and warnings. +// verification result and warnings. func NewVerifyHandler(printer *output.Printer) metadata.VerifyHandler { return text.NewVerifyHandler(printer) } + +// NewBlobVerifyHandler creates a new metadata BlobVerifyHandler for printing +// blob veriifcation result and warnings. +func NewBlobVerifyHandler(printer *output.Printer) metadata.BlobVerifyHandler { + return text.NewBlobVerifyHandler(printer) +} diff --git a/cmd/notation/internal/display/metadata/interface.go b/cmd/notation/internal/display/metadata/interface.go index 2eb18bd7c..fb1e342aa 100644 --- a/cmd/notation/internal/display/metadata/interface.go +++ b/cmd/notation/internal/display/metadata/interface.go @@ -54,3 +54,16 @@ type VerifyHandler interface { // outcomes must not be nil or empty. OnVerifySucceeded(outcomes []*notation.VerificationOutcome, digestReference string) } + +// BlobVerifyHandler is a handler for rendering metadata information of +// blob verification outcome. +// +// It only supports text format for now. +type BlobVerifyHandler interface { + Renderer + + // OnVerifySucceeded sets the successful verification result for the handler. + // + // outcomes must not be nil or empty. + OnVerifySucceeded(outcomes []*notation.VerificationOutcome, blobPath string) +} diff --git a/cmd/notation/internal/display/metadata/text/base.go b/cmd/notation/internal/display/metadata/text/base.go new file mode 100644 index 000000000..f21e857e9 --- /dev/null +++ b/cmd/notation/internal/display/metadata/text/base.go @@ -0,0 +1,76 @@ +// 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 text provides the text output in human-readable format for metadata +// information. +package text + +import ( + "fmt" + "reflect" + "text/tabwriter" + + "github.com/notaryproject/notation-go" + "github.com/notaryproject/notation-go/verifier/trustpolicy" + "github.com/notaryproject/notation/cmd/notation/internal/display/output" +) + +// PrintVerificationSuccess prints out messages when verification succeeds +func PrintVerificationSuccess(printer *output.Printer, outcome *notation.VerificationOutcome, printout string, hasWarning bool) error { + // write out on success + // print out warning for any failed result with logged verification action + for _, result := range outcome.VerificationResults { + if result.Error != nil { + // at this point, the verification action has to be logged and + // it's failed + printer.PrintErrorf("Warning: %v was set to %q and failed with error: %v\n", result.Type, result.Action, result.Error) + hasWarning = true + } + } + if hasWarning { + // print a newline to separate the warning from the final message + printer.Println() + } + if reflect.DeepEqual(outcome.VerificationLevel, trustpolicy.LevelSkip) { + printer.Println("Trust policy is configured to skip signature verification for", printout) + } else { + printer.Println("Successfully verified signature for", printout) + PrintMetadataIfPresent(printer, outcome) + } + return nil +} + +// PrintMetadataIfPresent prints out user metadata if present +func PrintMetadataIfPresent(printer *output.Printer, outcome *notation.VerificationOutcome) { + // the signature envelope is parsed as part of verification. + // since user metadata is only printed on successful verification, + // this error can be ignored + metadata, _ := outcome.UserMetadata() + + if len(metadata) > 0 { + printer.Println("\nThe artifact was signed with the following user metadata.") + PrintMetadataMap(printer, metadata) + } +} + +// PrintMetadataMap prints out metadata given the metatdata map +func PrintMetadataMap(printer *output.Printer, metadata map[string]string) error { + tw := tabwriter.NewWriter(printer, 0, 0, 3, ' ', 0) + fmt.Fprintln(tw, "\nKEY\tVALUE\t") + + for k, v := range metadata { + fmt.Fprintf(tw, "%v\t%v\t\n", k, v) + } + + return tw.Flush() +} diff --git a/cmd/notation/internal/display/metadata/text/verify_test.go b/cmd/notation/internal/display/metadata/text/base_test.go similarity index 97% rename from cmd/notation/internal/display/metadata/text/verify_test.go rename to cmd/notation/internal/display/metadata/text/base_test.go index 77caa475b..a587d293d 100644 --- a/cmd/notation/internal/display/metadata/text/verify_test.go +++ b/cmd/notation/internal/display/metadata/text/base_test.go @@ -47,7 +47,7 @@ func TestPrintMetadataIfPresent(t *testing.T) { buf := bytes.Buffer{} printer := output.NewPrinter(&buf, &buf) h := NewVerifyHandler(printer) - h.printMetadataIfPresent(outcome) + PrintMetadataIfPresent(h.printer, outcome) got := buf.String() expected := "\nThe artifact was signed with the following user metadata.\n\nKEY VALUE \nfoo bar \n" if got != expected { diff --git a/cmd/notation/internal/display/metadata/text/blobverify.go b/cmd/notation/internal/display/metadata/text/blobverify.go new file mode 100644 index 000000000..bc9fd494b --- /dev/null +++ b/cmd/notation/internal/display/metadata/text/blobverify.go @@ -0,0 +1,48 @@ +// 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 text + +import ( + "github.com/notaryproject/notation-go" + "github.com/notaryproject/notation/cmd/notation/internal/display/output" +) + +// BlobVerifyHandler is a handler for rendering output for blob verify command +// in human-readable format. +type BlobVerifyHandler struct { + printer *output.Printer + + outcome *notation.VerificationOutcome + blobPath string +} + +// NewBlobVerifyHandler creates a new BlobVerifyHandler. +func NewBlobVerifyHandler(printer *output.Printer) *BlobVerifyHandler { + return &BlobVerifyHandler{ + printer: printer, + } +} + +// OnVerifySucceeded sets the successful verification result for the handler. +// +// outcomes must not be nil or empty. +func (h *BlobVerifyHandler) OnVerifySucceeded(outcomes []*notation.VerificationOutcome, blobPath string) { + h.outcome = outcomes[0] + h.blobPath = blobPath +} + +// Render prints out the verification results in human-readable format. +func (h *BlobVerifyHandler) Render() error { + return PrintVerificationSuccess(h.printer, h.outcome, h.blobPath, false) +} diff --git a/cmd/notation/internal/display/metadata/text/verify.go b/cmd/notation/internal/display/metadata/text/verify.go index 838ad541f..5cfba15de 100644 --- a/cmd/notation/internal/display/metadata/text/verify.go +++ b/cmd/notation/internal/display/metadata/text/verify.go @@ -11,17 +11,10 @@ // See the License for the specific language governing permissions and // limitations under the License. -// Package text provides the text output in human-readable format for metadata -// information. package text import ( - "fmt" - "reflect" - "text/tabwriter" - "github.com/notaryproject/notation-go" - "github.com/notaryproject/notation-go/verifier/trustpolicy" "github.com/notaryproject/notation/cmd/notation/internal/display/output" ) @@ -35,8 +28,7 @@ type VerifyHandler struct { hasWarning bool } -// NewVerifyHandler creates a VerifyHandler to render verification results in -// human-readable format. +// NewVerifyHandler creates a new VerifyHandler. func NewVerifyHandler(printer *output.Printer) *VerifyHandler { return &VerifyHandler{ printer: printer, @@ -59,51 +51,5 @@ func (h *VerifyHandler) OnVerifySucceeded(outcomes []*notation.VerificationOutco // Render prints out the verification results in human-readable format. func (h *VerifyHandler) Render() error { - // write out on success - // print out warning for any failed result with logged verification action - for _, result := range h.outcome.VerificationResults { - if result.Error != nil { - // at this point, the verification action has to be logged and - // it's failed - h.printer.PrintErrorf("Warning: %v was set to %q and failed with error: %v\n", result.Type, result.Action, result.Error) - h.hasWarning = true - } - } - if h.hasWarning { - // print a newline to separate the warning from the final message - h.printer.Println() - } - if reflect.DeepEqual(h.outcome.VerificationLevel, trustpolicy.LevelSkip) { - h.printer.Println("Trust policy is configured to skip signature verification for", h.digestReference) - } else { - h.printer.Println("Successfully verified signature for", h.digestReference) - h.printMetadataIfPresent(h.outcome) - } - return nil -} - -func (h *VerifyHandler) printMetadataIfPresent(outcome *notation.VerificationOutcome) { - // the signature envelope is parsed as part of verification. - // since user metadata is only printed on successful verification, - // this error can be ignored - metadata, _ := outcome.UserMetadata() - - if len(metadata) > 0 { - h.printer.Println("\nThe artifact was signed with the following user metadata.") - h.printMetadataMap(metadata) - } -} - -// printMetadataMap prints out metadata given the metatdata map -// -// The metadata is additional information of text output. -func (h *VerifyHandler) printMetadataMap(metadata map[string]string) error { - tw := tabwriter.NewWriter(h.printer, 0, 0, 3, ' ', 0) - fmt.Fprintln(tw, "\nKEY\tVALUE\t") - - for k, v := range metadata { - fmt.Fprintf(tw, "%v\t%v\t\n", k, v) - } - - return tw.Flush() + return PrintVerificationSuccess(h.printer, h.outcome, h.digestReference, h.hasWarning) } diff --git a/cmd/notation/verify.go b/cmd/notation/verify.go index 7a01478e3..c5fe53153 100644 --- a/cmd/notation/verify.go +++ b/cmd/notation/verify.go @@ -16,11 +16,9 @@ package main import ( "errors" "fmt" - "io/fs" "os" "github.com/notaryproject/notation-go" - "github.com/notaryproject/notation-go/verifier/truststore" "github.com/notaryproject/notation/cmd/notation/internal/display" "github.com/notaryproject/notation/cmd/notation/internal/experimental" "github.com/notaryproject/notation/cmd/notation/internal/option" @@ -112,9 +110,8 @@ func runVerify(command *cobra.Command, opts *verifyOpts) error { // set log level ctx := opts.LoggingFlagOpts.InitializeLogger(command.Context()) - displayHandler := display.NewVerifyHandler(opts.Printer) - // initialize + displayHandler := display.NewVerifyHandler(opts.Printer) sigVerifier, err := cmd.GetVerifier(ctx, false) if err != nil { return err @@ -161,35 +158,3 @@ func runVerify(command *cobra.Command, opts *verifyOpts) error { displayHandler.OnVerifySucceeded(outcomes, resolvedRef) return displayHandler.Render() } - -func checkVerificationFailure(outcomes []*notation.VerificationOutcome, printOut string, err error) error { - // write out on failure - if err != nil || len(outcomes) == 0 { - if err != nil { - var errTrustStore truststore.TrustStoreError - if errors.As(err, &errTrustStore) { - if errors.Is(err, fs.ErrNotExist) { - return fmt.Errorf("%w. Use command 'notation cert add' to create and add trusted certificates to the trust store", errTrustStore) - } else { - return fmt.Errorf("%w. %w", errTrustStore, errTrustStore.InnerError) - } - } - - var errCertificate truststore.CertificateError - if errors.As(err, &errCertificate) { - if errors.Is(err, fs.ErrNotExist) { - return fmt.Errorf("%w. Use command 'notation cert add' to create and add trusted certificates to the trust store", errCertificate) - } else { - return fmt.Errorf("%w. %w", errCertificate, errCertificate.InnerError) - } - } - - var errorVerificationFailed notation.ErrorVerificationFailed - if !errors.As(err, &errorVerificationFailed) { - return fmt.Errorf("signature verification failed: %w", err) - } - } - return fmt.Errorf("signature verification failed for all the signatures associated with %s", printOut) - } - return nil -} diff --git a/internal/ioutil/print.go b/internal/ioutil/print.go index 9440af286..2a6cf258f 100644 --- a/internal/ioutil/print.go +++ b/internal/ioutil/print.go @@ -19,14 +19,11 @@ import ( "fmt" "io" "io/fs" - "os" "path/filepath" - "reflect" "text/tabwriter" "github.com/notaryproject/notation-go" "github.com/notaryproject/notation-go/config" - "github.com/notaryproject/notation-go/verifier/trustpolicy" "github.com/notaryproject/notation-go/verifier/truststore" ) @@ -122,48 +119,3 @@ func PrintVerificationFailure(outcomes []*notation.VerificationOutcome, printOut } return nil } - -// PrintVerificationSuccess prints out messages when verification succeeds -func PrintVerificationSuccess(outcomes []*notation.VerificationOutcome, printout string) { - // write out on success - outcome := outcomes[0] - // print out warning for any failed result with logged verification action - for _, result := range outcome.VerificationResults { - if result.Error != nil { - // at this point, the verification action has to be logged and - // it's failed - fmt.Fprintf(os.Stderr, "Warning: %v was set to %q and failed with error: %v\n", result.Type, result.Action, result.Error) - } - } - if reflect.DeepEqual(outcome.VerificationLevel, trustpolicy.LevelSkip) { - fmt.Println("Trust policy is configured to skip signature verification for", printout) - } else { - fmt.Println("Successfully verified signature for", printout) - PrintMetadataIfPresent(outcome) - } -} - -// PrintMetadataIfPresent prints out user metadata if present -func PrintMetadataIfPresent(outcome *notation.VerificationOutcome) { - // the signature envelope is parsed as part of verification. - // since user metadata is only printed on successful verification, - // this error can be ignored - metadata, _ := outcome.UserMetadata() - - if len(metadata) > 0 { - fmt.Println("\nThe artifact was signed with the following user metadata.") - PrintMetadataMap(os.Stdout, metadata) - } -} - -// PrintMetadataMap prints out metadata given the metatdata map -func PrintMetadataMap(w io.Writer, metadata map[string]string) error { - tw := newTabWriter(w) - fmt.Fprintln(tw, "\nKEY\tVALUE\t") - - for k, v := range metadata { - fmt.Fprintf(tw, "%v\t%v\t\n", k, v) - } - - return tw.Flush() -} From 399b72a9351bc5c996894e491e26a3f3794e95a4 Mon Sep 17 00:00:00 2001 From: Patrick Zheng Date: Tue, 11 Feb 2025 18:08:53 +0800 Subject: [PATCH 39/53] resolve conflicts Signed-off-by: Patrick Zheng --- test/e2e/internal/notation/init.go | 5 ----- 1 file changed, 5 deletions(-) diff --git a/test/e2e/internal/notation/init.go b/test/e2e/internal/notation/init.go index 45d5c0979..1bd92b9ac 100644 --- a/test/e2e/internal/notation/init.go +++ b/test/e2e/internal/notation/init.go @@ -24,13 +24,8 @@ import ( const ( NotationDirName = "notation" -<<<<<<< HEAD BlobTrustPolicyName = "trustpolicy.blob.json" TrustPolicyName = "trustpolicy.json" -======= - TrustPolicyName = "trustpolicy.json" - BlobTrustPolicyName = "trustpolicy.blob.json" ->>>>>>> ac77b58e5bc1e63d9adc432ba714ec250f8712a1 TrustStoreDirName = "truststore" TrustStoreTypeCA = "ca" PluginDirName = "plugins" From 2e05ecac0ea5f3579cd36228eebe0d0dfe7adc10 Mon Sep 17 00:00:00 2001 From: Patrick Zheng Date: Tue, 11 Feb 2025 18:15:10 +0800 Subject: [PATCH 40/53] blob verify Signed-off-by: Patrick Zheng --- cmd/notation/blob/verify.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/cmd/notation/blob/verify.go b/cmd/notation/blob/verify.go index cdafa7e79..9c6c95b4a 100644 --- a/cmd/notation/blob/verify.go +++ b/cmd/notation/blob/verify.go @@ -154,9 +154,15 @@ func runVerify(command *cobra.Command, cmdOpts *blobVerifyOpts) error { // parseSignatureMediaType returns the media type of the signature file. // `application/jose+json` and `application/cose` are supported. +// +// A valid signature file name has at least 3 parts. +// For example, myFile.jws.sig. func parseSignatureMediaType(signaturePath string) (string, error) { signatureFileName := filepath.Base(signaturePath) sigFilenameArr := strings.Split(signatureFileName, ".") + if len(sigFilenameArr) < 3 { + return "", fmt.Errorf("invalid signature filename %s", signatureFileName) + } format := sigFilenameArr[len(sigFilenameArr)-2] switch format { case "cose": From 5e2dbbec5bfc54a5f61e40b6f7fe05a0e697ef1a Mon Sep 17 00:00:00 2001 From: Patrick Zheng Date: Tue, 11 Feb 2025 18:15:49 +0800 Subject: [PATCH 41/53] blob verify Signed-off-by: Patrick Zheng --- cmd/notation/blob/verify.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cmd/notation/blob/verify.go b/cmd/notation/blob/verify.go index 9c6c95b4a..c9c053329 100644 --- a/cmd/notation/blob/verify.go +++ b/cmd/notation/blob/verify.go @@ -154,12 +154,12 @@ func runVerify(command *cobra.Command, cmdOpts *blobVerifyOpts) error { // parseSignatureMediaType returns the media type of the signature file. // `application/jose+json` and `application/cose` are supported. -// -// A valid signature file name has at least 3 parts. -// For example, myFile.jws.sig. func parseSignatureMediaType(signaturePath string) (string, error) { signatureFileName := filepath.Base(signaturePath) sigFilenameArr := strings.Split(signatureFileName, ".") + + // a valid signature file name has at least 3 parts. + // for example, myFile.jws.sig. if len(sigFilenameArr) < 3 { return "", fmt.Errorf("invalid signature filename %s", signatureFileName) } From 3b15156c15b6f5ee06dbabf351cd3d6cf6c303ec Mon Sep 17 00:00:00 2001 From: Patrick Zheng Date: Wed, 12 Feb 2025 11:08:30 +0800 Subject: [PATCH 42/53] blob verify Signed-off-by: Patrick Zheng --- cmd/notation/blob/verify.go | 3 +-- .../internal/display/metadata/text/base.go | 17 ++++++-------- .../display/metadata/text/base_test.go | 2 +- .../display/metadata/text/blobverify.go | 4 ++-- .../internal/display/metadata/text/verify.go | 3 +-- specs/commandline/blob.md | 23 ++++++++----------- 6 files changed, 22 insertions(+), 30 deletions(-) diff --git a/cmd/notation/blob/verify.go b/cmd/notation/blob/verify.go index c9c053329..f0c2c1875 100644 --- a/cmd/notation/blob/verify.go +++ b/cmd/notation/blob/verify.go @@ -128,7 +128,6 @@ func runVerify(command *cobra.Command, cmdOpts *blobVerifyOpts) error { if err != nil { return err } - signatureMediaType, err := parseSignatureMediaType(cmdOpts.signaturePath) if err != nil { return err @@ -159,7 +158,7 @@ func parseSignatureMediaType(signaturePath string) (string, error) { sigFilenameArr := strings.Split(signatureFileName, ".") // a valid signature file name has at least 3 parts. - // for example, myFile.jws.sig. + // for example, `myFile.jws.sig` if len(sigFilenameArr) < 3 { return "", fmt.Errorf("invalid signature filename %s", signatureFileName) } diff --git a/cmd/notation/internal/display/metadata/text/base.go b/cmd/notation/internal/display/metadata/text/base.go index f21e857e9..e6844ff9c 100644 --- a/cmd/notation/internal/display/metadata/text/base.go +++ b/cmd/notation/internal/display/metadata/text/base.go @@ -45,32 +45,29 @@ func PrintVerificationSuccess(printer *output.Printer, outcome *notation.Verific printer.Println("Trust policy is configured to skip signature verification for", printout) } else { printer.Println("Successfully verified signature for", printout) - PrintMetadataIfPresent(printer, outcome) + PrintUserMetadataIfPresent(printer, outcome) } return nil } -// PrintMetadataIfPresent prints out user metadata if present -func PrintMetadataIfPresent(printer *output.Printer, outcome *notation.VerificationOutcome) { +// PrintUserMetadataIfPresent prints out user metadata if present +func PrintUserMetadataIfPresent(printer *output.Printer, outcome *notation.VerificationOutcome) { // the signature envelope is parsed as part of verification. // since user metadata is only printed on successful verification, - // this error can be ignored + // this error can be ignored. metadata, _ := outcome.UserMetadata() - if len(metadata) > 0 { printer.Println("\nThe artifact was signed with the following user metadata.") - PrintMetadataMap(printer, metadata) + printUserMetadataMap(printer, metadata) } } -// PrintMetadataMap prints out metadata given the metatdata map -func PrintMetadataMap(printer *output.Printer, metadata map[string]string) error { +// printUserMetadataMap prints out user metadata map +func printUserMetadataMap(printer *output.Printer, metadata map[string]string) error { tw := tabwriter.NewWriter(printer, 0, 0, 3, ' ', 0) fmt.Fprintln(tw, "\nKEY\tVALUE\t") - for k, v := range metadata { fmt.Fprintf(tw, "%v\t%v\t\n", k, v) } - return tw.Flush() } diff --git a/cmd/notation/internal/display/metadata/text/base_test.go b/cmd/notation/internal/display/metadata/text/base_test.go index a587d293d..0466f849f 100644 --- a/cmd/notation/internal/display/metadata/text/base_test.go +++ b/cmd/notation/internal/display/metadata/text/base_test.go @@ -47,7 +47,7 @@ func TestPrintMetadataIfPresent(t *testing.T) { buf := bytes.Buffer{} printer := output.NewPrinter(&buf, &buf) h := NewVerifyHandler(printer) - PrintMetadataIfPresent(h.printer, outcome) + PrintUserMetadataIfPresent(h.printer, outcome) got := buf.String() expected := "\nThe artifact was signed with the following user metadata.\n\nKEY VALUE \nfoo bar \n" if got != expected { diff --git a/cmd/notation/internal/display/metadata/text/blobverify.go b/cmd/notation/internal/display/metadata/text/blobverify.go index bc9fd494b..03b13143b 100644 --- a/cmd/notation/internal/display/metadata/text/blobverify.go +++ b/cmd/notation/internal/display/metadata/text/blobverify.go @@ -20,9 +20,9 @@ import ( // BlobVerifyHandler is a handler for rendering output for blob verify command // in human-readable format. +// It implements metadata/BlobVerifyHandler. type BlobVerifyHandler struct { - printer *output.Printer - + printer *output.Printer outcome *notation.VerificationOutcome blobPath string } diff --git a/cmd/notation/internal/display/metadata/text/verify.go b/cmd/notation/internal/display/metadata/text/verify.go index 5cfba15de..4d64de15b 100644 --- a/cmd/notation/internal/display/metadata/text/verify.go +++ b/cmd/notation/internal/display/metadata/text/verify.go @@ -21,8 +21,7 @@ import ( // VerifyHandler is a handler for rendering output for verify command in // human-readable format. type VerifyHandler struct { - printer *output.Printer - + printer *output.Printer outcome *notation.VerificationOutcome digestReference string hasWarning bool diff --git a/specs/commandline/blob.md b/specs/commandline/blob.md index 6d683b287..1e33048e4 100644 --- a/specs/commandline/blob.md +++ b/specs/commandline/blob.md @@ -4,7 +4,7 @@ Use `notation blob` command to sign, verify, and inspect signatures associated with arbitrary blobs. Notation can sign and verify any arbitrary bag of bits like zip files, documents, executables, etc. When a user signs a blob, `notation` produces a detached signature, which the user can transport/distribute using any medium that the user prefers along with the original blob. On the verification side, Notation can verify the blob's signature and assert that the blob has not been tampered with during its transmission. -Users can use `notation blob policy` command to manage trust policies for verifying a blob signature. The `notation blob policy` command provides a user-friendly way to manage trust policies for signed blobs. It allows users to show trust policy configuration, import/export a trust policy configuration file from/to a JSON file. For more details, see [blob trust policy specification and examples](https://github.com/notaryproject/specifications/blob/main/specs/trust-store-trust-policy.md#blob-trust-policy). +The `notation blob policy` command provides a user-friendly way to manage trust policies for signed blobs. It allows users to show blob trust policy configuration, import/export a blob trust policy configuration file from/to a JSON file. For more details, see [blob trust policy specification and examples](https://github.com/notaryproject/specifications/blob/main/specs/trust-store-trust-policy.md#blob-trust-policy). The sample trust policy file (`trustpolicy.blob.json`) for verifying signed blobs is shown below. This sample trust policy file, contains three different statements for different use cases: @@ -173,7 +173,7 @@ Flags: ## Usage ## Produce blob signatures -The signature file will be written to the currently working directory with file name "{blob file name}.{signature format}.sig". For example, signature file name for "myBlob.bin" will be "myBlob.bin.jws.sig" for JWS signature format or "myBlob.bin.cose.sig" for COSE signature format. +The signature file will be written to the currently working directory with file name `{blob file name}.{signature format}.sig`. For example, signature file name for "myBlob.bin" will be "myBlob.bin.jws.sig" for JWS signature format or "myBlob.bin.cose.sig" for COSE signature format. ### Sign a blob by adding a new key @@ -431,7 +431,7 @@ notation blob verify --signature /tmp/my-blob.bin.jws.sig /tmp/my-blob.bin An example of output messages for a successful verification: ```text -Successfully verified signature /tmp/my-blob.bin.jws.sig +Successfully verified signature for /tmp/my-blob.bin ``` ### Verify the signature with user metadata @@ -446,7 +446,7 @@ notation blob verify --user-metadata io.wabbit-networks.buildId=123 --signature An example of output messages for a successful verification: ```text -Successfully verified signature /tmp/my-blob.bin.jws.sig +Successfully verified signature for /tmp/my-blob.bin The signature contains the following user metadata: @@ -457,7 +457,7 @@ io.wabbit-networks.buildId 123 An example of output messages for an unsuccessful verification: ```text -Error: signature verification failed: unable to find specified metadata in the given signature +Error: signature verification failed: unable to find specified metadata in the signature ``` ### Verify the signature with media type @@ -472,16 +472,13 @@ notation blob verify --media-type application/my-media-octet-stream --signature An example of output messages for a successful verification: ```text -Successfully verified signature /tmp/my-blob.bin.jws.sig - -The blob is of media type `application/my-media-octet-stream`. - +Successfully verified signature for /tmp/my-blob.bin ``` -An example of output messages for an unsuccessful verification: +An example of output messages for a failed verification: ```text -Error: Signature verification failed due to a mismatch in the blob's media type 'application/xyz' and the expected type 'application/my-media-octet-stream'. +Error: signature verification failed: integrity check failed. signature does not match the given blob ``` ### Verify the signature using a policy name @@ -495,11 +492,11 @@ notation blob verify --policy-name wabbit-networks-policy --signature ./sigs/my- An example of output messages for a successful verification: ```text -Successfully verified signature ./sigs/my-blob.bin.jws.sig using policy `wabbit-networks-policy` +Successfully verified signature for ./blobs/my-blob.bin ``` An example of output messages for an unsuccessful verification: ```text -Error: signature verification failed for policy `wabbit-networks-policy` +Error: signature verification failed: no applicable blob trust policy with name "wabbit-networks-policy" ``` From b0f6bd7bb90c00e84e8ad28f8fd864cdf5ba8730 Mon Sep 17 00:00:00 2001 From: Patrick Zheng Date: Wed, 12 Feb 2025 11:14:45 +0800 Subject: [PATCH 43/53] blob verify Signed-off-by: Patrick Zheng --- test/e2e/run.sh | 2 +- test/e2e/testdata/blob/{blobFile => blobFile.txt} | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename test/e2e/testdata/blob/{blobFile => blobFile.txt} (100%) diff --git a/test/e2e/run.sh b/test/e2e/run.sh index af9aae7d7..419367332 100755 --- a/test/e2e/run.sh +++ b/test/e2e/run.sh @@ -116,7 +116,7 @@ export NOTATION_E2E_TEST_TAG=v1 export NOTATION_E2E_PLUGIN_PATH=$CWD/plugin/bin/$PLUGIN_NAME export NOTATION_E2E_PLUGIN_TAR_GZ_PATH=$CWD/plugin/bin/$PLUGIN_NAME.tar.gz export NOTATION_E2E_MALICIOUS_PLUGIN_ARCHIVE_PATH=$CWD/testdata/malicious-plugin -export NOTATION_E2E_BLOB_PATH=$CWD/testdata/blob/blobFile +export NOTATION_E2E_BLOB_PATH=$CWD/testdata/blob/blobFile.txt export NOTATION_E2E_BLOB_TRUST_POLICY_PATH=$CWD/testdata/blob/trustpolicies # run tests diff --git a/test/e2e/testdata/blob/blobFile b/test/e2e/testdata/blob/blobFile.txt similarity index 100% rename from test/e2e/testdata/blob/blobFile rename to test/e2e/testdata/blob/blobFile.txt From db669c017948676a097940d5b4b10659776abd61 Mon Sep 17 00:00:00 2001 From: Patrick Zheng Date: Wed, 12 Feb 2025 11:19:04 +0800 Subject: [PATCH 44/53] fix e2e test Signed-off-by: Patrick Zheng --- test/e2e/suite/command/blob/sign.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/e2e/suite/command/blob/sign.go b/test/e2e/suite/command/blob/sign.go index 3e5ab269c..19cd74bd0 100644 --- a/test/e2e/suite/command/blob/sign.go +++ b/test/e2e/suite/command/blob/sign.go @@ -76,7 +76,7 @@ var _ = Describe("notation blob sign", func() { HostWithBlob(BaseOptions(), func(notation *utils.ExecOpts, blobPath string, vhost *utils.VirtualHost) { notation.Exec("blob", "sign", "--signature-directory", vhost.AbsolutePath(), blobPath). MatchKeyWords(SignSuccessfully). - MatchKeyWords(fmt.Sprintf("Signature file written to %s", vhost.AbsolutePath("blobFile.jws.sig"))) + MatchKeyWords(fmt.Sprintf("Signature file written to %s", vhost.AbsolutePath("blobFile.txt.jws.sig"))) }) }) @@ -101,11 +101,11 @@ var _ = Describe("notation blob sign", func() { sigDir := vhost.AbsolutePath() notation.Exec("blob", "sign", "--signature-directory", sigDir, blobPath). MatchKeyWords(SignSuccessfully). - MatchKeyWords(fmt.Sprintf("Signature file written to %s", vhost.AbsolutePath("blobFile.jws.sig"))) + MatchKeyWords(fmt.Sprintf("Signature file written to %s", vhost.AbsolutePath("blobFile.txt.jws.sig"))) notation.Exec("blob", "sign", "--force", "--signature-directory", sigDir, blobPath). MatchKeyWords(SignSuccessfully). - MatchKeyWords(fmt.Sprintf("Signature file written to %s", vhost.AbsolutePath("blobFile.jws.sig"))) + MatchKeyWords(fmt.Sprintf("Signature file written to %s", vhost.AbsolutePath("blobFile.txt.jws.sig"))) }) }) From 3e223d089f75422924fb349710b2c19589d45226 Mon Sep 17 00:00:00 2001 From: Patrick Zheng Date: Wed, 12 Feb 2025 16:28:38 +0800 Subject: [PATCH 45/53] update Signed-off-by: Patrick Zheng --- cmd/notation/blob/verify.go | 18 ++++----- cmd/notation/internal/display/handler.go | 2 +- .../internal/display/metadata/text/base.go | 10 ++--- .../display/metadata/text/base_test.go | 2 +- .../display/metadata/text/blobverify.go | 2 +- .../internal/display/metadata/text/verify.go | 2 +- test/e2e/suite/command/blob/verify.go | 37 ++++++++++++++++++- 7 files changed, 52 insertions(+), 21 deletions(-) diff --git a/cmd/notation/blob/verify.go b/cmd/notation/blob/verify.go index f0c2c1875..229112f50 100644 --- a/cmd/notation/blob/verify.go +++ b/cmd/notation/blob/verify.go @@ -20,12 +20,11 @@ import ( "path/filepath" "strings" - "github.com/notaryproject/notation-core-go/signature/cose" - "github.com/notaryproject/notation-core-go/signature/jws" "github.com/notaryproject/notation-go" "github.com/notaryproject/notation/cmd/notation/internal/display" "github.com/notaryproject/notation/cmd/notation/internal/option" "github.com/notaryproject/notation/internal/cmd" + "github.com/notaryproject/notation/internal/envelope" "github.com/notaryproject/notation/internal/ioutil" "github.com/spf13/cobra" ) @@ -155,19 +154,16 @@ func runVerify(command *cobra.Command, cmdOpts *blobVerifyOpts) error { // `application/jose+json` and `application/cose` are supported. func parseSignatureMediaType(signaturePath string) (string, error) { signatureFileName := filepath.Base(signaturePath) + if filepath.Ext(signatureFileName) != ".sig" { + return "", fmt.Errorf("invalid signature filename %s. The file extension must be .sig", signatureFileName) + } sigFilenameArr := strings.Split(signatureFileName, ".") // a valid signature file name has at least 3 parts. // for example, `myFile.jws.sig` if len(sigFilenameArr) < 3 { - return "", fmt.Errorf("invalid signature filename %s", signatureFileName) - } - format := sigFilenameArr[len(sigFilenameArr)-2] - switch format { - case "cose": - return cose.MediaTypeEnvelope, nil - case "jws": - return jws.MediaTypeEnvelope, nil + return "", fmt.Errorf("invalid signature filename %s. A valid signature file name must contain signature format and .sig file extension", signatureFileName) } - return "", fmt.Errorf("unsupported signature format %s", format) + sigFormat := sigFilenameArr[len(sigFilenameArr)-2] + return envelope.GetEnvelopeMediaType(sigFormat) } diff --git a/cmd/notation/internal/display/handler.go b/cmd/notation/internal/display/handler.go index f23ce96cb..02cec17f1 100644 --- a/cmd/notation/internal/display/handler.go +++ b/cmd/notation/internal/display/handler.go @@ -49,7 +49,7 @@ func NewVerifyHandler(printer *output.Printer) metadata.VerifyHandler { } // NewBlobVerifyHandler creates a new metadata BlobVerifyHandler for printing -// blob veriifcation result and warnings. +// blob verification result and warnings. func NewBlobVerifyHandler(printer *output.Printer) metadata.BlobVerifyHandler { return text.NewBlobVerifyHandler(printer) } diff --git a/cmd/notation/internal/display/metadata/text/base.go b/cmd/notation/internal/display/metadata/text/base.go index e6844ff9c..299b4db90 100644 --- a/cmd/notation/internal/display/metadata/text/base.go +++ b/cmd/notation/internal/display/metadata/text/base.go @@ -25,8 +25,8 @@ import ( "github.com/notaryproject/notation/cmd/notation/internal/display/output" ) -// PrintVerificationSuccess prints out messages when verification succeeds -func PrintVerificationSuccess(printer *output.Printer, outcome *notation.VerificationOutcome, printout string, hasWarning bool) error { +// printVerificationSuccess prints out messages when verification succeeds +func printVerificationSuccess(printer *output.Printer, outcome *notation.VerificationOutcome, printout string, hasWarning bool) error { // write out on success // print out warning for any failed result with logged verification action for _, result := range outcome.VerificationResults { @@ -45,13 +45,13 @@ func PrintVerificationSuccess(printer *output.Printer, outcome *notation.Verific printer.Println("Trust policy is configured to skip signature verification for", printout) } else { printer.Println("Successfully verified signature for", printout) - PrintUserMetadataIfPresent(printer, outcome) + printUserMetadataIfPresent(printer, outcome) } return nil } -// PrintUserMetadataIfPresent prints out user metadata if present -func PrintUserMetadataIfPresent(printer *output.Printer, outcome *notation.VerificationOutcome) { +// printUserMetadataIfPresent prints out user metadata if present +func printUserMetadataIfPresent(printer *output.Printer, outcome *notation.VerificationOutcome) { // the signature envelope is parsed as part of verification. // since user metadata is only printed on successful verification, // this error can be ignored. diff --git a/cmd/notation/internal/display/metadata/text/base_test.go b/cmd/notation/internal/display/metadata/text/base_test.go index 0466f849f..ce5abf7fe 100644 --- a/cmd/notation/internal/display/metadata/text/base_test.go +++ b/cmd/notation/internal/display/metadata/text/base_test.go @@ -47,7 +47,7 @@ func TestPrintMetadataIfPresent(t *testing.T) { buf := bytes.Buffer{} printer := output.NewPrinter(&buf, &buf) h := NewVerifyHandler(printer) - PrintUserMetadataIfPresent(h.printer, outcome) + printUserMetadataIfPresent(h.printer, outcome) got := buf.String() expected := "\nThe artifact was signed with the following user metadata.\n\nKEY VALUE \nfoo bar \n" if got != expected { diff --git a/cmd/notation/internal/display/metadata/text/blobverify.go b/cmd/notation/internal/display/metadata/text/blobverify.go index 03b13143b..6160b3865 100644 --- a/cmd/notation/internal/display/metadata/text/blobverify.go +++ b/cmd/notation/internal/display/metadata/text/blobverify.go @@ -44,5 +44,5 @@ func (h *BlobVerifyHandler) OnVerifySucceeded(outcomes []*notation.VerificationO // Render prints out the verification results in human-readable format. func (h *BlobVerifyHandler) Render() error { - return PrintVerificationSuccess(h.printer, h.outcome, h.blobPath, false) + return printVerificationSuccess(h.printer, h.outcome, h.blobPath, false) } diff --git a/cmd/notation/internal/display/metadata/text/verify.go b/cmd/notation/internal/display/metadata/text/verify.go index 4d64de15b..743e17d4a 100644 --- a/cmd/notation/internal/display/metadata/text/verify.go +++ b/cmd/notation/internal/display/metadata/text/verify.go @@ -50,5 +50,5 @@ func (h *VerifyHandler) OnVerifySucceeded(outcomes []*notation.VerificationOutco // Render prints out the verification results in human-readable format. func (h *VerifyHandler) Render() error { - return PrintVerificationSuccess(h.printer, h.outcome, h.digestReference, h.hasWarning) + return printVerificationSuccess(h.printer, h.outcome, h.digestReference, h.hasWarning) } diff --git a/test/e2e/suite/command/blob/verify.go b/test/e2e/suite/command/blob/verify.go index c18093716..22c8eb45e 100644 --- a/test/e2e/suite/command/blob/verify.go +++ b/test/e2e/suite/command/blob/verify.go @@ -17,6 +17,7 @@ import ( "fmt" "os" "path/filepath" + "strings" . "github.com/notaryproject/notation/test/e2e/internal/notation" "github.com/notaryproject/notation/test/e2e/internal/utils" @@ -201,6 +202,40 @@ var _ = Describe("notation blob verify", func() { }) }) + It("with invalid signature file extension", func() { + HostWithBlob(BaseBlobOptions(), func(notation *utils.ExecOpts, blobPath string, vhost *utils.VirtualHost) { + workDir := vhost.AbsolutePath() + notation.WithWorkDir(workDir).Exec("blob", "sign", blobPath). + MatchKeyWords(SignSuccessfully). + MatchKeyWords("Signature file written to") + + signaturePath := signatureFilepath(workDir, blobPath, "jws") + invalidSignaturePath := strings.TrimSuffix(signaturePath, ".sig") + "." + "invalid" + if err := os.Rename(signaturePath, invalidSignaturePath); err != nil { + Fail(err.Error()) + } + notation.ExpectFailure().Exec("blob", "verify", "--signature", invalidSignaturePath, blobPath). + MatchErrKeyWords(`invalid signature filename blobFile.txt.jws.invalid. The file extension must be .sig`) + }) + }) + + It("with invalid signature file name", func() { + HostWithBlob(BaseBlobOptions(), func(notation *utils.ExecOpts, blobPath string, vhost *utils.VirtualHost) { + workDir := vhost.AbsolutePath() + notation.WithWorkDir(workDir).Exec("blob", "sign", blobPath). + MatchKeyWords(SignSuccessfully). + MatchKeyWords("Signature file written to") + + signaturePath := signatureFilepath(workDir, blobPath, "jws") + invalidSignaturePath := strings.TrimSuffix(signaturePath, ".jws.sig") + if err := os.Rename(signaturePath, invalidSignaturePath); err != nil { + Fail(err.Error()) + } + notation.ExpectFailure().Exec("blob", "verify", "--signature", invalidSignaturePath, blobPath). + MatchErrKeyWords(`invalid signature filename blobFile.txt. A valid signature file name must contain signature format and .sig file extension`) + }) + }) + It("with invalid signature format", func() { HostWithBlob(BaseBlobOptions(), func(notation *utils.ExecOpts, blobPath string, vhost *utils.VirtualHost) { workDir := vhost.AbsolutePath() @@ -214,7 +249,7 @@ var _ = Describe("notation blob verify", func() { Fail(err.Error()) } notation.ExpectFailure().Exec("blob", "verify", "--signature", invalidSignaturePath, blobPath). - MatchErrKeyWords("unsupported signature format invalid") + MatchErrKeyWords(`signature format "invalid" not supported\nSupported signature envelope formats are "jws" and "cose"`) }) }) From e425c4db03386e26cf23dad72c079b96420f6f8e Mon Sep 17 00:00:00 2001 From: Patrick Zheng Date: Wed, 12 Feb 2025 16:35:01 +0800 Subject: [PATCH 46/53] fix e2e tests Signed-off-by: Patrick Zheng --- test/e2e/suite/command/blob/verify.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/test/e2e/suite/command/blob/verify.go b/test/e2e/suite/command/blob/verify.go index 22c8eb45e..fbd022cb0 100644 --- a/test/e2e/suite/command/blob/verify.go +++ b/test/e2e/suite/command/blob/verify.go @@ -227,7 +227,7 @@ var _ = Describe("notation blob verify", func() { MatchKeyWords("Signature file written to") signaturePath := signatureFilepath(workDir, blobPath, "jws") - invalidSignaturePath := strings.TrimSuffix(signaturePath, ".jws.sig") + invalidSignaturePath := strings.TrimSuffix(signaturePath, ".txt.jws.sig") + ".sig" if err := os.Rename(signaturePath, invalidSignaturePath); err != nil { Fail(err.Error()) } @@ -249,7 +249,8 @@ var _ = Describe("notation blob verify", func() { Fail(err.Error()) } notation.ExpectFailure().Exec("blob", "verify", "--signature", invalidSignaturePath, blobPath). - MatchErrKeyWords(`signature format "invalid" not supported\nSupported signature envelope formats are "jws" and "cose"`) + MatchErrKeyWords(`signature format "invalid" not supported`). + MatchErrKeyWords(`Supported signature envelope formats are "jws" and "cose"`) }) }) From ea9520459160d1a6f7954f3ba7c7f2256127bb23 Mon Sep 17 00:00:00 2001 From: Patrick Zheng Date: Wed, 12 Feb 2025 16:38:55 +0800 Subject: [PATCH 47/53] update Signed-off-by: Patrick Zheng --- .../internal/display/metadata/text/{base.go => helper.go} | 0 .../display/metadata/text/{base_test.go => helper_test.go} | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename cmd/notation/internal/display/metadata/text/{base.go => helper.go} (100%) rename cmd/notation/internal/display/metadata/text/{base_test.go => helper_test.go} (100%) diff --git a/cmd/notation/internal/display/metadata/text/base.go b/cmd/notation/internal/display/metadata/text/helper.go similarity index 100% rename from cmd/notation/internal/display/metadata/text/base.go rename to cmd/notation/internal/display/metadata/text/helper.go diff --git a/cmd/notation/internal/display/metadata/text/base_test.go b/cmd/notation/internal/display/metadata/text/helper_test.go similarity index 100% rename from cmd/notation/internal/display/metadata/text/base_test.go rename to cmd/notation/internal/display/metadata/text/helper_test.go From b9a0b4619834f5ad14255f2e1a710adda8b54624 Mon Sep 17 00:00:00 2001 From: Patrick Zheng Date: Wed, 12 Feb 2025 16:41:30 +0800 Subject: [PATCH 48/53] update Signed-off-by: Patrick Zheng --- test/e2e/suite/command/blob/verify.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/e2e/suite/command/blob/verify.go b/test/e2e/suite/command/blob/verify.go index fbd022cb0..b120e2735 100644 --- a/test/e2e/suite/command/blob/verify.go +++ b/test/e2e/suite/command/blob/verify.go @@ -232,7 +232,7 @@ var _ = Describe("notation blob verify", func() { Fail(err.Error()) } notation.ExpectFailure().Exec("blob", "verify", "--signature", invalidSignaturePath, blobPath). - MatchErrKeyWords(`invalid signature filename blobFile.txt. A valid signature file name must contain signature format and .sig file extension`) + MatchErrKeyWords(`invalid signature filename blobFile.sig. A valid signature file name must contain signature format and .sig file extension`) }) }) From ace6b69a07b25233f9a44c8b743d4d010356a20e Mon Sep 17 00:00:00 2001 From: Patrick Zheng Date: Thu, 13 Feb 2025 13:29:45 +0800 Subject: [PATCH 49/53] update Signed-off-by: Patrick Zheng --- cmd/notation/main.go | 1 - 1 file changed, 1 deletion(-) diff --git a/cmd/notation/main.go b/cmd/notation/main.go index be68016ff..7f33a1202 100644 --- a/cmd/notation/main.go +++ b/cmd/notation/main.go @@ -64,7 +64,6 @@ func main() { logoutCommand(nil), versionCommand(), inspectCommand(nil), - blob.Cmd(), ) if err := cmd.Execute(); err != nil { os.Exit(1) From cffbf3dbff4fd71357414074c2f18df6e31049b9 Mon Sep 17 00:00:00 2001 From: Patrick Zheng Date: Thu, 13 Feb 2025 18:39:38 +0800 Subject: [PATCH 50/53] update Signed-off-by: Patrick Zheng --- cmd/notation/blob/sign.go | 3 +- cmd/notation/blob/verify.go | 9 ++- .../internal/display/metadata/text/helper.go | 6 +- cmd/notation/sign.go | 3 +- cmd/notation/verify.go | 5 +- go.mod | 2 +- go.sum | 4 +- internal/cmd/{ => signer}/signer.go | 5 +- internal/cmd/{ => signer}/signer_test.go | 23 +++--- internal/cmd/{ => verifier}/verifier.go | 55 +++++++++----- internal/cmd/{ => verifier}/verifier_test.go | 67 +++++++++-------- internal/ioutil/print.go | 73 ++++++++++--------- internal/ioutil/print_test.go | 2 +- test/e2e/go.mod | 2 +- test/e2e/go.sum | 4 +- test/e2e/plugin/go.mod | 2 +- test/e2e/plugin/go.sum | 4 +- 17 files changed, 150 insertions(+), 119 deletions(-) rename internal/cmd/{ => signer}/signer.go (94%) rename internal/cmd/{ => signer}/signer_test.go (93%) rename internal/cmd/{ => verifier}/verifier.go (61%) rename internal/cmd/{ => verifier}/verifier_test.go (91%) diff --git a/cmd/notation/blob/sign.go b/cmd/notation/blob/sign.go index 635c51c63..3e5241f42 100644 --- a/cmd/notation/blob/sign.go +++ b/cmd/notation/blob/sign.go @@ -27,6 +27,7 @@ import ( "github.com/notaryproject/notation-go/log" "github.com/notaryproject/notation/cmd/notation/internal/cmdutil" "github.com/notaryproject/notation/internal/cmd" + "github.com/notaryproject/notation/internal/cmd/signer" "github.com/notaryproject/notation/internal/envelope" "github.com/notaryproject/notation/internal/httputil" "github.com/notaryproject/notation/internal/osutil" @@ -138,7 +139,7 @@ func runBlobSign(command *cobra.Command, cmdOpts *blobSignOpts) error { ctx := cmdOpts.LoggingFlagOpts.InitializeLogger(command.Context()) logger := log.GetLogger(ctx) - blobSigner, err := cmd.GetSigner(ctx, &cmdOpts.SignerFlagOpts) + blobSigner, err := signer.GetSigner(ctx, &cmdOpts.SignerFlagOpts) if err != nil { return err } diff --git a/cmd/notation/blob/verify.go b/cmd/notation/blob/verify.go index 229112f50..ec76eb717 100644 --- a/cmd/notation/blob/verify.go +++ b/cmd/notation/blob/verify.go @@ -24,6 +24,7 @@ import ( "github.com/notaryproject/notation/cmd/notation/internal/display" "github.com/notaryproject/notation/cmd/notation/internal/option" "github.com/notaryproject/notation/internal/cmd" + "github.com/notaryproject/notation/internal/cmd/verifier" "github.com/notaryproject/notation/internal/envelope" "github.com/notaryproject/notation/internal/ioutil" "github.com/spf13/cobra" @@ -111,7 +112,7 @@ func runVerify(command *cobra.Command, cmdOpts *blobVerifyOpts) error { if err != nil { return err } - blobVerifier, err := cmd.GetVerifier(ctx, true) + blobVerifier, err := verifier.GetBlobVerifier(ctx) if err != nil { return err } @@ -142,7 +143,7 @@ func runVerify(command *cobra.Command, cmdOpts *blobVerifyOpts) error { } _, outcome, err := notation.VerifyBlob(ctx, blobVerifier, blobFile, signatureBytes, verifyBlobOpts) outcomes := []*notation.VerificationOutcome{outcome} - err = ioutil.PrintVerificationFailure(outcomes, cmdOpts.blobPath, err, true) + err = ioutil.ComposeBlobVerificationFailurePrintout(outcomes, cmdOpts.blobPath, err) if err != nil { return err } @@ -154,7 +155,7 @@ func runVerify(command *cobra.Command, cmdOpts *blobVerifyOpts) error { // `application/jose+json` and `application/cose` are supported. func parseSignatureMediaType(signaturePath string) (string, error) { signatureFileName := filepath.Base(signaturePath) - if filepath.Ext(signatureFileName) != ".sig" { + if strings.ToLower(filepath.Ext(signatureFileName)) != ".sig" { return "", fmt.Errorf("invalid signature filename %s. The file extension must be .sig", signatureFileName) } sigFilenameArr := strings.Split(signatureFileName, ".") @@ -165,5 +166,5 @@ func parseSignatureMediaType(signaturePath string) (string, error) { return "", fmt.Errorf("invalid signature filename %s. A valid signature file name must contain signature format and .sig file extension", signatureFileName) } sigFormat := sigFilenameArr[len(sigFilenameArr)-2] - return envelope.GetEnvelopeMediaType(sigFormat) + return envelope.GetEnvelopeMediaType(strings.ToLower(sigFormat)) } diff --git a/cmd/notation/internal/display/metadata/text/helper.go b/cmd/notation/internal/display/metadata/text/helper.go index 299b4db90..2e5571648 100644 --- a/cmd/notation/internal/display/metadata/text/helper.go +++ b/cmd/notation/internal/display/metadata/text/helper.go @@ -26,7 +26,7 @@ import ( ) // printVerificationSuccess prints out messages when verification succeeds -func printVerificationSuccess(printer *output.Printer, outcome *notation.VerificationOutcome, printout string, hasWarning bool) error { +func printVerificationSuccess(printer *output.Printer, outcome *notation.VerificationOutcome, artifact string, hasWarning bool) error { // write out on success // print out warning for any failed result with logged verification action for _, result := range outcome.VerificationResults { @@ -42,9 +42,9 @@ func printVerificationSuccess(printer *output.Printer, outcome *notation.Verific printer.Println() } if reflect.DeepEqual(outcome.VerificationLevel, trustpolicy.LevelSkip) { - printer.Println("Trust policy is configured to skip signature verification for", printout) + printer.Println("Trust policy is configured to skip signature verification for", artifact) } else { - printer.Println("Successfully verified signature for", printout) + printer.Println("Successfully verified signature for", artifact) printUserMetadataIfPresent(printer, outcome) } return nil diff --git a/cmd/notation/sign.go b/cmd/notation/sign.go index 441d22947..cd4e4d124 100644 --- a/cmd/notation/sign.go +++ b/cmd/notation/sign.go @@ -27,6 +27,7 @@ import ( "github.com/notaryproject/notation-go/log" "github.com/notaryproject/notation/cmd/notation/internal/experimental" "github.com/notaryproject/notation/internal/cmd" + "github.com/notaryproject/notation/internal/cmd/signer" "github.com/notaryproject/notation/internal/envelope" "github.com/notaryproject/notation/internal/httputil" clirev "github.com/notaryproject/notation/internal/revocation" @@ -162,7 +163,7 @@ func runSign(command *cobra.Command, cmdOpts *signOpts) error { ctx := cmdOpts.LoggingFlagOpts.InitializeLogger(command.Context()) // initialize - signer, err := cmd.GetSigner(ctx, &cmdOpts.SignerFlagOpts) + signer, err := signer.GetSigner(ctx, &cmdOpts.SignerFlagOpts) if err != nil { return err } diff --git a/cmd/notation/verify.go b/cmd/notation/verify.go index c5fe53153..a445d7113 100644 --- a/cmd/notation/verify.go +++ b/cmd/notation/verify.go @@ -23,6 +23,7 @@ import ( "github.com/notaryproject/notation/cmd/notation/internal/experimental" "github.com/notaryproject/notation/cmd/notation/internal/option" "github.com/notaryproject/notation/internal/cmd" + "github.com/notaryproject/notation/internal/cmd/verifier" "github.com/notaryproject/notation/internal/ioutil" ocispec "github.com/opencontainers/image-spec/specs-go/v1" "github.com/spf13/cobra" @@ -112,7 +113,7 @@ func runVerify(command *cobra.Command, opts *verifyOpts) error { // initialize displayHandler := display.NewVerifyHandler(opts.Printer) - sigVerifier, err := cmd.GetVerifier(ctx, false) + sigVerifier, err := verifier.GetVerifier(ctx) if err != nil { return err } @@ -151,7 +152,7 @@ func runVerify(command *cobra.Command, opts *verifyOpts) error { UserMetadata: userMetadata, } _, outcomes, err := notation.Verify(ctx, sigVerifier, sigRepo, verifyOpts) - err = ioutil.PrintVerificationFailure(outcomes, resolvedRef, err, false) + err = ioutil.ComposeVerificationFailurePrintout(outcomes, resolvedRef, err) if err != nil { return err } diff --git a/go.mod b/go.mod index 53853d9b4..ba309938f 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,7 @@ go 1.23 require ( github.com/notaryproject/notation-core-go v1.2.0 - github.com/notaryproject/notation-go v1.2.0-beta.1.0.20250115061529-96b71337184c + github.com/notaryproject/notation-go v1.2.0-beta.1.0.20250122072255-6eb53a50d69e github.com/notaryproject/tspclient-go v1.0.0 github.com/opencontainers/go-digest v1.0.0 github.com/opencontainers/image-spec v1.1.0 diff --git a/go.sum b/go.sum index 078c85232..70b63ef5e 100644 --- a/go.sum +++ b/go.sum @@ -38,8 +38,8 @@ github.com/jcmturner/rpc/v2 v2.0.3 h1:7FXXj8Ti1IaVFpSAziCZWNzbNuZmnvw/i6CqLNdWfZ github.com/jcmturner/rpc/v2 v2.0.3/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJki3ELg/Hc= github.com/notaryproject/notation-core-go v1.2.0 h1:WElMG9X0YXJhBd0A4VOxLNalTLrTjvqtIAj7JHr5X08= github.com/notaryproject/notation-core-go v1.2.0/go.mod h1:+y3L1dOs2/ZwJIU5Imo7BBvZ/M3CFjXkydGGdK09EtA= -github.com/notaryproject/notation-go v1.2.0-beta.1.0.20250115061529-96b71337184c h1:P9GOhlDUtUXEWGikUDDIrnES2YtlYa15tTfg1U/E96Y= -github.com/notaryproject/notation-go v1.2.0-beta.1.0.20250115061529-96b71337184c/go.mod h1:ig6lhOPvLW4jrp6ZfaW+B3uNGKbcNW9pgIByvz/s31w= +github.com/notaryproject/notation-go v1.2.0-beta.1.0.20250122072255-6eb53a50d69e h1:1CyFPpzmL0PCRyj9UZLtvDKlGmTKBe9Q/Tx13lfoBVU= +github.com/notaryproject/notation-go v1.2.0-beta.1.0.20250122072255-6eb53a50d69e/go.mod h1:ig6lhOPvLW4jrp6ZfaW+B3uNGKbcNW9pgIByvz/s31w= 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 v1.0.0 h1:AwQ4x0gX8IHnyiZB1tggpn5NFqHpTEm1SDX8YNv4Dg4= diff --git a/internal/cmd/signer.go b/internal/cmd/signer/signer.go similarity index 94% rename from internal/cmd/signer.go rename to internal/cmd/signer/signer.go index c0c042026..942d419d7 100644 --- a/internal/cmd/signer.go +++ b/internal/cmd/signer/signer.go @@ -11,7 +11,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package cmd +package signer import ( "context" @@ -21,6 +21,7 @@ import ( "github.com/notaryproject/notation-go/dir" "github.com/notaryproject/notation-go/plugin" "github.com/notaryproject/notation-go/signer" + "github.com/notaryproject/notation/internal/cmd" "github.com/notaryproject/notation/pkg/configutil" ) @@ -31,7 +32,7 @@ type Signer interface { } // GetSigner returns a Signer based on user opts. -func GetSigner(ctx context.Context, opts *SignerFlagOpts) (Signer, error) { +func GetSigner(ctx context.Context, opts *cmd.SignerFlagOpts) (Signer, error) { // Check if using on-demand key if opts.KeyID != "" && opts.PluginName != "" && opts.Key == "" { // Construct a signer from on-demand key diff --git a/internal/cmd/signer_test.go b/internal/cmd/signer/signer_test.go similarity index 93% rename from internal/cmd/signer_test.go rename to internal/cmd/signer/signer_test.go index f41b14e0c..57ed2b760 100644 --- a/internal/cmd/signer_test.go +++ b/internal/cmd/signer/signer_test.go @@ -11,7 +11,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package cmd +package signer import ( "context" @@ -21,6 +21,7 @@ import ( "github.com/notaryproject/notation-go" "github.com/notaryproject/notation-go/dir" "github.com/notaryproject/notation-go/signer" + "github.com/notaryproject/notation/internal/cmd" ) func TestGenericSignerImpl(t *testing.T) { @@ -56,7 +57,7 @@ func TestGetSignerFromOpts(t *testing.T) { dir.UserLibexecDir = "./testdata/plugins" ctx := context.Background() - opts := &SignerFlagOpts{ + opts := &cmd.SignerFlagOpts{ KeyID: "testKeyId", PluginName: "testPlugin", } @@ -85,7 +86,7 @@ func TestGetSignerFromConfig(t *testing.T) { dir.UserLibexecDir = "./testdata/plugins" dir.UserConfigDir = "./testdata/valid_signingkeys" ctx := context.Background() - opts := &SignerFlagOpts{ + opts := &cmd.SignerFlagOpts{ Key: "test", } @@ -102,7 +103,7 @@ func TestGetSignerFromConfig(t *testing.T) { func TestGetFailed(t *testing.T) { ctx := context.Background() - opts := &SignerFlagOpts{} + opts := &cmd.SignerFlagOpts{} defer func(oldLibexeDir, oldConfigDir string) { dir.UserLibexecDir = oldLibexeDir @@ -126,7 +127,7 @@ func TestGetSignerFailed(t *testing.T) { }(dir.UserLibexecDir, dir.UserConfigDir) t.Run("get failed", func(t *testing.T) { - opts := &SignerFlagOpts{} + opts := &cmd.SignerFlagOpts{} dir.UserLibexecDir = "./testdata/plugins" dir.UserConfigDir = "./testdata/invalid_signingkeys" _, err := GetSigner(ctx, opts) @@ -142,7 +143,7 @@ func TestGetSignerFailed(t *testing.T) { dir.UserLibexecDir = "./testdata/plugins" dir.UserConfigDir = "./testdata/invalid_signingkeys" - opts := &SignerFlagOpts{ + opts := &cmd.SignerFlagOpts{ KeyID: "test", PluginName: "invalid", } @@ -156,7 +157,7 @@ func TestGetSignerFailed(t *testing.T) { t.Run("failed to resolve key", func(t *testing.T) { dir.UserConfigDir = "./testdata/valid_signingkeys" expectedErrMsg := `default signing key not set. Please set default signing key or specify a key name` - _, err := GetSigner(ctx, &SignerFlagOpts{}) + _, err := GetSigner(ctx, &cmd.SignerFlagOpts{}) if err == nil || err.Error() != expectedErrMsg { t.Fatalf("expected %s, but got %s", expectedErrMsg, err) } @@ -165,7 +166,7 @@ func TestGetSignerFailed(t *testing.T) { t.Run("keypath not specified", func(t *testing.T) { dir.UserConfigDir = "./testdata/invalid_signingkeys" expectedErrMsg := `key path not specified` - opts := &SignerFlagOpts{ + opts := &cmd.SignerFlagOpts{ Key: "invalid", } _, err := GetSigner(ctx, opts) @@ -177,7 +178,7 @@ func TestGetSignerFailed(t *testing.T) { t.Run("key not found", func(t *testing.T) { dir.UserConfigDir = "./testdata/valid_signingkeys" expectedErrMsg := `signing key not found` - opts := &SignerFlagOpts{ + opts := &cmd.SignerFlagOpts{ Key: "test2", } _, err := GetSigner(ctx, opts) @@ -194,7 +195,7 @@ func TestGetSignerFailed(t *testing.T) { dir.UserLibexecDir = "./testdata/plugins" dir.UserConfigDir = "./testdata/invalid_signingkeys" expectedErrMsg := `plugin executable file is either not found or inaccessible: stat testdata/plugins/plugins/invalid/notation-invalid: no such file or directory` - opts := &SignerFlagOpts{ + opts := &cmd.SignerFlagOpts{ Key: "invalidExternal", } _, err := GetSigner(ctx, opts) @@ -206,7 +207,7 @@ func TestGetSignerFailed(t *testing.T) { t.Run("empty key", func(t *testing.T) { dir.UserConfigDir = "./testdata/invalid_signingkeys" expectedErrMsg := `unsupported key, either provide a local key and certificate file paths, or a key name in config.json, check https://notaryproject.dev/docs/user-guides/how-to/notation-config-file/ for details` - opts := &SignerFlagOpts{ + opts := &cmd.SignerFlagOpts{ Key: "empty", } _, err := GetSigner(ctx, opts) diff --git a/internal/cmd/verifier.go b/internal/cmd/verifier/verifier.go similarity index 61% rename from internal/cmd/verifier.go rename to internal/cmd/verifier/verifier.go index 9ff15cde2..f6417b535 100644 --- a/internal/cmd/verifier.go +++ b/internal/cmd/verifier/verifier.go @@ -11,7 +11,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package cmd +package verifier import ( "context" @@ -33,38 +33,53 @@ type Verifier interface { notation.Verifier } -// GetVerifier returns a Verifier. -// isBlob is set to true when verifying an arbitrary blob. -func GetVerifier(ctx context.Context, isBlob bool) (Verifier, error) { - // revocation check - revocationCodeSigningValidator, err := clirev.NewRevocationValidator(ctx, purpose.CodeSigning) +// GetVerifier creates a Verifier. +func GetVerifier(ctx context.Context) (Verifier, error) { + verifierOptions, err := newVerifierOptions(ctx) if err != nil { return nil, err } - revocationTimestampingValidator, err := clirev.NewRevocationValidator(ctx, purpose.Timestamping) + + // trust policy and trust store + x509TrustStore := truststore.NewX509TrustStore(dir.ConfigFS()) + policyDocument, err := trustpolicy.LoadOCIDocument() + if err != nil { + return nil, err + } + verifierOptions.OCITrustPolicy = policyDocument + return verifier.NewVerifierWithOptions(x509TrustStore, verifierOptions) +} + +// GetBlobVerifier creates a BlobVerifier. +func GetBlobVerifier(ctx context.Context) (Verifier, error) { + verifierOptions, err := newVerifierOptions(ctx) if err != nil { return nil, err } // trust policy and trust store x509TrustStore := truststore.NewX509TrustStore(dir.ConfigFS()) - if isBlob { - blobPolicyDocument, err := trustpolicy.LoadBlobDocument() - if err != nil { - return nil, err - } - return verifier.NewVerifierWithOptions(nil, blobPolicyDocument, x509TrustStore, plugin.NewCLIManager(dir.PluginFS()), verifier.VerifierOptions{ - RevocationCodeSigningValidator: revocationCodeSigningValidator, - RevocationTimestampingValidator: revocationTimestampingValidator, - }) + blobPolicyDocument, err := trustpolicy.LoadBlobDocument() + if err != nil { + return nil, err } + verifierOptions.BlobTrustPolicy = blobPolicyDocument + return verifier.NewVerifierWithOptions(x509TrustStore, verifierOptions) +} - policyDocument, err := trustpolicy.LoadOCIDocument() +// newVerifierOptions creates a verifier.VerifierOptions. +func newVerifierOptions(ctx context.Context) (verifier.VerifierOptions, error) { + revocationCodeSigningValidator, err := clirev.NewRevocationValidator(ctx, purpose.CodeSigning) if err != nil { - return nil, err + return verifier.VerifierOptions{}, err + } + revocationTimestampingValidator, err := clirev.NewRevocationValidator(ctx, purpose.Timestamping) + if err != nil { + return verifier.VerifierOptions{}, err } - return verifier.NewVerifierWithOptions(policyDocument, nil, x509TrustStore, plugin.NewCLIManager(dir.PluginFS()), verifier.VerifierOptions{ + return verifier.VerifierOptions{ RevocationCodeSigningValidator: revocationCodeSigningValidator, RevocationTimestampingValidator: revocationTimestampingValidator, - }) + PluginManager: plugin.NewCLIManager(dir.PluginFS()), + }, nil } diff --git a/internal/cmd/verifier_test.go b/internal/cmd/verifier/verifier_test.go similarity index 91% rename from internal/cmd/verifier_test.go rename to internal/cmd/verifier/verifier_test.go index 15791382e..aee9dcc7d 100644 --- a/internal/cmd/verifier_test.go +++ b/internal/cmd/verifier/verifier_test.go @@ -11,7 +11,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package cmd +package verifier import ( "context" @@ -40,23 +40,7 @@ func TestGetVerifier(t *testing.T) { } t.Cleanup(func() { os.RemoveAll(tempRoot) }) - _, err := GetVerifier(context.Background(), false) - if err != nil { - t.Fatal(err) - } - }) - - t.Run("blob success", func(t *testing.T) { - tempRoot := t.TempDir() - dir.UserConfigDir = tempRoot - path := filepath.Join(tempRoot, "trustpolicy.blob.json") - policyJson, _ := json.Marshal(dummyBlobPolicyDocument(false)) - if err := os.WriteFile(path, policyJson, 0600); err != nil { - t.Fatalf("write blob policy file failed. Error: %v", err) - } - t.Cleanup(func() { os.RemoveAll(tempRoot) }) - - _, err := GetVerifier(context.Background(), true) + _, err := GetVerifier(context.Background()) if err != nil { t.Fatal(err) } @@ -65,16 +49,7 @@ func TestGetVerifier(t *testing.T) { t.Run("non-existing oci trust policy", func(t *testing.T) { dir.UserConfigDir = "/" expectedErrMsg := "trust policy is not present. To create a trust policy, see: https://notaryproject.dev/docs/quickstart/#create-a-trust-policy" - _, err := GetVerifier(context.Background(), false) - if err == nil || err.Error() != expectedErrMsg { - t.Fatalf("expected %s, but got %s", expectedErrMsg, err) - } - }) - - t.Run("non-existing blob trust policy", func(t *testing.T) { - dir.UserConfigDir = "/" - expectedErrMsg := "trust policy is not present. To create a trust policy, see: https://notaryproject.dev/docs/quickstart/#create-a-trust-policy" - _, err := GetVerifier(context.Background(), true) + _, err := GetVerifier(context.Background()) if err == nil || err.Error() != expectedErrMsg { t.Fatalf("expected %s, but got %s", expectedErrMsg, err) } @@ -91,7 +66,39 @@ func TestGetVerifier(t *testing.T) { t.Cleanup(func() { os.RemoveAll(tempRoot) }) expectedErrMsg := "oci trust policy document has empty version, version must be specified" - _, err := GetVerifier(context.Background(), false) + _, err := GetVerifier(context.Background()) + if err == nil || err.Error() != expectedErrMsg { + t.Fatalf("expected %s, but got %s", expectedErrMsg, err) + } + }) +} + +func TestGetBlobVerifier(t *testing.T) { + defer func(oldConfiDir, oldCacheDir string) { + dir.UserConfigDir = oldConfiDir + dir.UserCacheDir = oldCacheDir + }(dir.UserConfigDir, dir.UserCacheDir) + + t.Run("blob success", func(t *testing.T) { + tempRoot := t.TempDir() + dir.UserConfigDir = tempRoot + path := filepath.Join(tempRoot, "trustpolicy.blob.json") + policyJson, _ := json.Marshal(dummyBlobPolicyDocument(false)) + if err := os.WriteFile(path, policyJson, 0600); err != nil { + t.Fatalf("write blob policy file failed. Error: %v", err) + } + t.Cleanup(func() { os.RemoveAll(tempRoot) }) + + _, err := GetBlobVerifier(context.Background()) + if err != nil { + t.Fatal(err) + } + }) + + t.Run("non-existing blob trust policy", func(t *testing.T) { + dir.UserConfigDir = "/" + expectedErrMsg := "trust policy is not present. To create a trust policy, see: https://notaryproject.dev/docs/quickstart/#create-a-trust-policy" + _, err := GetBlobVerifier(context.Background()) if err == nil || err.Error() != expectedErrMsg { t.Fatalf("expected %s, but got %s", expectedErrMsg, err) } @@ -108,7 +115,7 @@ func TestGetVerifier(t *testing.T) { t.Cleanup(func() { os.RemoveAll(tempRoot) }) expectedErrMsg := "blob trust policy document has empty version, version must be specified" - _, err := GetVerifier(context.Background(), true) + _, err := GetBlobVerifier(context.Background()) if err == nil || err.Error() != expectedErrMsg { t.Fatalf("expected %s, but got %s", expectedErrMsg, err) } diff --git a/internal/ioutil/print.go b/internal/ioutil/print.go index 2a6cf258f..32d0466b7 100644 --- a/internal/ioutil/print.go +++ b/internal/ioutil/print.go @@ -14,7 +14,6 @@ package ioutil import ( - "encoding/json" "errors" "fmt" "io" @@ -72,50 +71,54 @@ func PrintCertMap(w io.Writer, certPaths []string) error { return tw.Flush() } -// PrintObjectAsJSON takes an interface and prints it as an indented JSON string -func PrintObjectAsJSON(i interface{}) error { - jsonBytes, err := json.MarshalIndent(i, "", " ") - if err != nil { - return err +// ComposeVerificationFailurePrintout composes verification failure print out. +func ComposeVerificationFailurePrintout(outcomes []*notation.VerificationOutcome, reference string, err error) error { + if verificationErr := parseErrorOnVerificationFailure(err); verificationErr != nil { + return verificationErr + } + if len(outcomes) == 0 { + return fmt.Errorf("signature verification failed for all the signatures associated with %s", reference) } - - fmt.Println(string(jsonBytes)) - return nil } -// PrintVerificationFailure prints out messages when verification fails -func PrintVerificationFailure(outcomes []*notation.VerificationOutcome, printOut string, err error, isBlob bool) error { - // write out on failure - if err != nil || len(outcomes) == 0 { - if err != nil { - var errTrustStore truststore.TrustStoreError - if errors.As(err, &errTrustStore) { - if errors.Is(err, fs.ErrNotExist) { - return fmt.Errorf("%w. Use command 'notation cert add' to create and add trusted certificates to the trust store", errTrustStore) - } else { - return fmt.Errorf("%w. %w", errTrustStore, errTrustStore.InnerError) - } - } +// ComposeBlobVerificationFailurePrintout composes blob verification failure +// print out. +func ComposeBlobVerificationFailurePrintout(outcomes []*notation.VerificationOutcome, blobPath string, err error) error { + if verificationErr := parseErrorOnVerificationFailure(err); verificationErr != nil { + return verificationErr + } + if len(outcomes) == 0 { + return fmt.Errorf("provided signature verification failed against blob %s", blobPath) + } + return nil +} - var errCertificate truststore.CertificateError - if errors.As(err, &errCertificate) { - if errors.Is(err, fs.ErrNotExist) { - return fmt.Errorf("%w. Use command 'notation cert add' to create and add trusted certificates to the trust store", errCertificate) - } else { - return fmt.Errorf("%w. %w", errCertificate, errCertificate.InnerError) - } +// parseErrorOnVerificationFailure parses error on verification failure. +func parseErrorOnVerificationFailure(err error) error { + if err != nil { + var errTrustStore truststore.TrustStoreError + if errors.As(err, &errTrustStore) { + if errors.Is(err, fs.ErrNotExist) { + return fmt.Errorf("%w. Use command 'notation cert add' to create and add trusted certificates to the trust store", errTrustStore) + } else { + return fmt.Errorf("%w. %w", errTrustStore, errTrustStore.InnerError) } + } - var errorVerificationFailed notation.ErrorVerificationFailed - if !errors.As(err, &errorVerificationFailed) { - return fmt.Errorf("signature verification failed: %w", err) + var errCertificate truststore.CertificateError + if errors.As(err, &errCertificate) { + if errors.Is(err, fs.ErrNotExist) { + return fmt.Errorf("%w. Use command 'notation cert add' to create and add trusted certificates to the trust store", errCertificate) + } else { + return fmt.Errorf("%w. %w", errCertificate, errCertificate.InnerError) } } - if isBlob { - return fmt.Errorf("provided signature verification failed against blob %s", printOut) + + var errorVerificationFailed notation.ErrorVerificationFailed + if !errors.As(err, &errorVerificationFailed) { + return fmt.Errorf("signature verification failed: %w", err) } - return fmt.Errorf("signature verification failed for all the signatures associated with %s", printOut) } return nil } diff --git a/internal/ioutil/print_test.go b/internal/ioutil/print_test.go index 02c976ce5..f158eb097 100644 --- a/internal/ioutil/print_test.go +++ b/internal/ioutil/print_test.go @@ -22,7 +22,7 @@ import ( func TestBlobVerificateFailure(t *testing.T) { var outcomes []*notation.VerificationOutcome expectedErrMsg := "provided signature verification failed against blob myblob" - err := PrintVerificationFailure(outcomes, "myblob", nil, true) + err := ComposeBlobVerificationFailurePrintout(outcomes, "myblob", nil) if err == nil || err.Error() != expectedErrMsg { t.Fatalf("expected %s, but got %s", expectedErrMsg, err) } diff --git a/test/e2e/go.mod b/test/e2e/go.mod index ac2516729..2d8cc118f 100644 --- a/test/e2e/go.mod +++ b/test/e2e/go.mod @@ -4,7 +4,7 @@ go 1.23 require ( github.com/notaryproject/notation-core-go v1.2.0 - github.com/notaryproject/notation-go v1.2.0-beta.1.0.20250115061529-96b71337184c + github.com/notaryproject/notation-go v1.2.0-beta.1.0.20250122072255-6eb53a50d69e github.com/onsi/ginkgo/v2 v2.22.2 github.com/onsi/gomega v1.36.2 github.com/opencontainers/image-spec v1.1.0 diff --git a/test/e2e/go.sum b/test/e2e/go.sum index bb8f7cee1..c2474b975 100644 --- a/test/e2e/go.sum +++ b/test/e2e/go.sum @@ -12,8 +12,8 @@ github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad h1:a6HEuzUHeKH6hwfN/Z github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144= github.com/notaryproject/notation-core-go v1.2.0 h1:WElMG9X0YXJhBd0A4VOxLNalTLrTjvqtIAj7JHr5X08= github.com/notaryproject/notation-core-go v1.2.0/go.mod h1:+y3L1dOs2/ZwJIU5Imo7BBvZ/M3CFjXkydGGdK09EtA= -github.com/notaryproject/notation-go v1.2.0-beta.1.0.20250115061529-96b71337184c h1:P9GOhlDUtUXEWGikUDDIrnES2YtlYa15tTfg1U/E96Y= -github.com/notaryproject/notation-go v1.2.0-beta.1.0.20250115061529-96b71337184c/go.mod h1:ig6lhOPvLW4jrp6ZfaW+B3uNGKbcNW9pgIByvz/s31w= +github.com/notaryproject/notation-go v1.2.0-beta.1.0.20250122072255-6eb53a50d69e h1:1CyFPpzmL0PCRyj9UZLtvDKlGmTKBe9Q/Tx13lfoBVU= +github.com/notaryproject/notation-go v1.2.0-beta.1.0.20250122072255-6eb53a50d69e/go.mod h1:ig6lhOPvLW4jrp6ZfaW+B3uNGKbcNW9pgIByvz/s31w= 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/onsi/ginkgo/v2 v2.22.2 h1:/3X8Panh8/WwhU/3Ssa6rCKqPLuAkVY2I0RoyDLySlU= diff --git a/test/e2e/plugin/go.mod b/test/e2e/plugin/go.mod index 59e9b01f4..88c194ce1 100644 --- a/test/e2e/plugin/go.mod +++ b/test/e2e/plugin/go.mod @@ -5,7 +5,7 @@ go 1.23 require ( github.com/golang-jwt/jwt v3.2.2+incompatible github.com/notaryproject/notation-core-go v1.2.0 - github.com/notaryproject/notation-go v1.2.0-beta.1.0.20250115061529-96b71337184c + github.com/notaryproject/notation-go v1.2.0-beta.1.0.20250122072255-6eb53a50d69e github.com/notaryproject/notation-plugin-framework-go v1.0.0 github.com/spf13/cobra v1.8.1 ) diff --git a/test/e2e/plugin/go.sum b/test/e2e/plugin/go.sum index 5f12603c7..152aa3f42 100644 --- a/test/e2e/plugin/go.sum +++ b/test/e2e/plugin/go.sum @@ -40,8 +40,8 @@ github.com/jcmturner/rpc/v2 v2.0.3 h1:7FXXj8Ti1IaVFpSAziCZWNzbNuZmnvw/i6CqLNdWfZ github.com/jcmturner/rpc/v2 v2.0.3/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJki3ELg/Hc= github.com/notaryproject/notation-core-go v1.2.0 h1:WElMG9X0YXJhBd0A4VOxLNalTLrTjvqtIAj7JHr5X08= github.com/notaryproject/notation-core-go v1.2.0/go.mod h1:+y3L1dOs2/ZwJIU5Imo7BBvZ/M3CFjXkydGGdK09EtA= -github.com/notaryproject/notation-go v1.2.0-beta.1.0.20250115061529-96b71337184c h1:P9GOhlDUtUXEWGikUDDIrnES2YtlYa15tTfg1U/E96Y= -github.com/notaryproject/notation-go v1.2.0-beta.1.0.20250115061529-96b71337184c/go.mod h1:ig6lhOPvLW4jrp6ZfaW+B3uNGKbcNW9pgIByvz/s31w= +github.com/notaryproject/notation-go v1.2.0-beta.1.0.20250122072255-6eb53a50d69e h1:1CyFPpzmL0PCRyj9UZLtvDKlGmTKBe9Q/Tx13lfoBVU= +github.com/notaryproject/notation-go v1.2.0-beta.1.0.20250122072255-6eb53a50d69e/go.mod h1:ig6lhOPvLW4jrp6ZfaW+B3uNGKbcNW9pgIByvz/s31w= 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 v1.0.0 h1:AwQ4x0gX8IHnyiZB1tggpn5NFqHpTEm1SDX8YNv4Dg4= From 0e906a4b5472030ca3b23eb482fb67dd0a8d179d Mon Sep 17 00:00:00 2001 From: Patrick Zheng Date: Thu, 13 Feb 2025 18:47:34 +0800 Subject: [PATCH 51/53] update Signed-off-by: Patrick Zheng --- internal/cmd/{ => signer}/testdata/config.json | 0 .../{ => signer}/testdata/invalid_signingkeys/signingkeys.json | 0 .../testdata/plugins/plugins/testPlugin/notation-testPlugin | 0 .../cmd/{ => signer}/testdata/valid_signingkeys/signingkeys.json | 0 4 files changed, 0 insertions(+), 0 deletions(-) rename internal/cmd/{ => signer}/testdata/config.json (100%) rename internal/cmd/{ => signer}/testdata/invalid_signingkeys/signingkeys.json (100%) rename internal/cmd/{ => signer}/testdata/plugins/plugins/testPlugin/notation-testPlugin (100%) rename internal/cmd/{ => signer}/testdata/valid_signingkeys/signingkeys.json (100%) diff --git a/internal/cmd/testdata/config.json b/internal/cmd/signer/testdata/config.json similarity index 100% rename from internal/cmd/testdata/config.json rename to internal/cmd/signer/testdata/config.json diff --git a/internal/cmd/testdata/invalid_signingkeys/signingkeys.json b/internal/cmd/signer/testdata/invalid_signingkeys/signingkeys.json similarity index 100% rename from internal/cmd/testdata/invalid_signingkeys/signingkeys.json rename to internal/cmd/signer/testdata/invalid_signingkeys/signingkeys.json diff --git a/internal/cmd/testdata/plugins/plugins/testPlugin/notation-testPlugin b/internal/cmd/signer/testdata/plugins/plugins/testPlugin/notation-testPlugin similarity index 100% rename from internal/cmd/testdata/plugins/plugins/testPlugin/notation-testPlugin rename to internal/cmd/signer/testdata/plugins/plugins/testPlugin/notation-testPlugin diff --git a/internal/cmd/testdata/valid_signingkeys/signingkeys.json b/internal/cmd/signer/testdata/valid_signingkeys/signingkeys.json similarity index 100% rename from internal/cmd/testdata/valid_signingkeys/signingkeys.json rename to internal/cmd/signer/testdata/valid_signingkeys/signingkeys.json From 35d5e0c87d3f8db93525370407bccf50b09005d9 Mon Sep 17 00:00:00 2001 From: Patrick Zheng Date: Fri, 14 Feb 2025 10:08:54 +0800 Subject: [PATCH 52/53] update Signed-off-by: Patrick Zheng --- .../display/metadata/text/{helper.go => verify_helper.go} | 0 .../metadata/text/{helper_test.go => verify_helper_test.go} | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename cmd/notation/internal/display/metadata/text/{helper.go => verify_helper.go} (100%) rename cmd/notation/internal/display/metadata/text/{helper_test.go => verify_helper_test.go} (100%) diff --git a/cmd/notation/internal/display/metadata/text/helper.go b/cmd/notation/internal/display/metadata/text/verify_helper.go similarity index 100% rename from cmd/notation/internal/display/metadata/text/helper.go rename to cmd/notation/internal/display/metadata/text/verify_helper.go diff --git a/cmd/notation/internal/display/metadata/text/helper_test.go b/cmd/notation/internal/display/metadata/text/verify_helper_test.go similarity index 100% rename from cmd/notation/internal/display/metadata/text/helper_test.go rename to cmd/notation/internal/display/metadata/text/verify_helper_test.go From 4c3571c995d405b529aa3e600df3e551ec90a07c Mon Sep 17 00:00:00 2001 From: Patrick Zheng Date: Fri, 14 Feb 2025 10:48:07 +0800 Subject: [PATCH 53/53] update Signed-off-by: Patrick Zheng --- cmd/notation/blob/sign.go | 2 +- cmd/notation/blob/verify.go | 2 +- .../notation/internal}/signer/signer.go | 0 .../notation/internal}/signer/signer_test.go | 0 .../internal}/signer/testdata/config.json | 0 .../invalid_signingkeys/signingkeys.json | 0 .../plugins/testPlugin/notation-testPlugin | 0 .../valid_signingkeys/signingkeys.json | 0 .../notation/internal}/verifier/verifier.go | 0 .../internal}/verifier/verifier_test.go | 0 cmd/notation/sign.go | 2 +- cmd/notation/verify.go | 2 +- internal/ioutil/print.go | 40 ++++++++++--------- 13 files changed, 25 insertions(+), 23 deletions(-) rename {internal/cmd => cmd/notation/internal}/signer/signer.go (100%) rename {internal/cmd => cmd/notation/internal}/signer/signer_test.go (100%) rename {internal/cmd => cmd/notation/internal}/signer/testdata/config.json (100%) rename {internal/cmd => cmd/notation/internal}/signer/testdata/invalid_signingkeys/signingkeys.json (100%) rename {internal/cmd => cmd/notation/internal}/signer/testdata/plugins/plugins/testPlugin/notation-testPlugin (100%) rename {internal/cmd => cmd/notation/internal}/signer/testdata/valid_signingkeys/signingkeys.json (100%) rename {internal/cmd => cmd/notation/internal}/verifier/verifier.go (100%) rename {internal/cmd => cmd/notation/internal}/verifier/verifier_test.go (100%) diff --git a/cmd/notation/blob/sign.go b/cmd/notation/blob/sign.go index 3e5241f42..452fcbb1c 100644 --- a/cmd/notation/blob/sign.go +++ b/cmd/notation/blob/sign.go @@ -26,8 +26,8 @@ import ( "github.com/notaryproject/notation-go" "github.com/notaryproject/notation-go/log" "github.com/notaryproject/notation/cmd/notation/internal/cmdutil" + "github.com/notaryproject/notation/cmd/notation/internal/signer" "github.com/notaryproject/notation/internal/cmd" - "github.com/notaryproject/notation/internal/cmd/signer" "github.com/notaryproject/notation/internal/envelope" "github.com/notaryproject/notation/internal/httputil" "github.com/notaryproject/notation/internal/osutil" diff --git a/cmd/notation/blob/verify.go b/cmd/notation/blob/verify.go index ec76eb717..131c05858 100644 --- a/cmd/notation/blob/verify.go +++ b/cmd/notation/blob/verify.go @@ -23,8 +23,8 @@ import ( "github.com/notaryproject/notation-go" "github.com/notaryproject/notation/cmd/notation/internal/display" "github.com/notaryproject/notation/cmd/notation/internal/option" + "github.com/notaryproject/notation/cmd/notation/internal/verifier" "github.com/notaryproject/notation/internal/cmd" - "github.com/notaryproject/notation/internal/cmd/verifier" "github.com/notaryproject/notation/internal/envelope" "github.com/notaryproject/notation/internal/ioutil" "github.com/spf13/cobra" diff --git a/internal/cmd/signer/signer.go b/cmd/notation/internal/signer/signer.go similarity index 100% rename from internal/cmd/signer/signer.go rename to cmd/notation/internal/signer/signer.go diff --git a/internal/cmd/signer/signer_test.go b/cmd/notation/internal/signer/signer_test.go similarity index 100% rename from internal/cmd/signer/signer_test.go rename to cmd/notation/internal/signer/signer_test.go diff --git a/internal/cmd/signer/testdata/config.json b/cmd/notation/internal/signer/testdata/config.json similarity index 100% rename from internal/cmd/signer/testdata/config.json rename to cmd/notation/internal/signer/testdata/config.json diff --git a/internal/cmd/signer/testdata/invalid_signingkeys/signingkeys.json b/cmd/notation/internal/signer/testdata/invalid_signingkeys/signingkeys.json similarity index 100% rename from internal/cmd/signer/testdata/invalid_signingkeys/signingkeys.json rename to cmd/notation/internal/signer/testdata/invalid_signingkeys/signingkeys.json diff --git a/internal/cmd/signer/testdata/plugins/plugins/testPlugin/notation-testPlugin b/cmd/notation/internal/signer/testdata/plugins/plugins/testPlugin/notation-testPlugin similarity index 100% rename from internal/cmd/signer/testdata/plugins/plugins/testPlugin/notation-testPlugin rename to cmd/notation/internal/signer/testdata/plugins/plugins/testPlugin/notation-testPlugin diff --git a/internal/cmd/signer/testdata/valid_signingkeys/signingkeys.json b/cmd/notation/internal/signer/testdata/valid_signingkeys/signingkeys.json similarity index 100% rename from internal/cmd/signer/testdata/valid_signingkeys/signingkeys.json rename to cmd/notation/internal/signer/testdata/valid_signingkeys/signingkeys.json diff --git a/internal/cmd/verifier/verifier.go b/cmd/notation/internal/verifier/verifier.go similarity index 100% rename from internal/cmd/verifier/verifier.go rename to cmd/notation/internal/verifier/verifier.go diff --git a/internal/cmd/verifier/verifier_test.go b/cmd/notation/internal/verifier/verifier_test.go similarity index 100% rename from internal/cmd/verifier/verifier_test.go rename to cmd/notation/internal/verifier/verifier_test.go diff --git a/cmd/notation/sign.go b/cmd/notation/sign.go index cd4e4d124..215b759c2 100644 --- a/cmd/notation/sign.go +++ b/cmd/notation/sign.go @@ -26,8 +26,8 @@ import ( "github.com/notaryproject/notation-go" "github.com/notaryproject/notation-go/log" "github.com/notaryproject/notation/cmd/notation/internal/experimental" + "github.com/notaryproject/notation/cmd/notation/internal/signer" "github.com/notaryproject/notation/internal/cmd" - "github.com/notaryproject/notation/internal/cmd/signer" "github.com/notaryproject/notation/internal/envelope" "github.com/notaryproject/notation/internal/httputil" clirev "github.com/notaryproject/notation/internal/revocation" diff --git a/cmd/notation/verify.go b/cmd/notation/verify.go index a445d7113..ecfec843a 100644 --- a/cmd/notation/verify.go +++ b/cmd/notation/verify.go @@ -22,8 +22,8 @@ import ( "github.com/notaryproject/notation/cmd/notation/internal/display" "github.com/notaryproject/notation/cmd/notation/internal/experimental" "github.com/notaryproject/notation/cmd/notation/internal/option" + "github.com/notaryproject/notation/cmd/notation/internal/verifier" "github.com/notaryproject/notation/internal/cmd" - "github.com/notaryproject/notation/internal/cmd/verifier" "github.com/notaryproject/notation/internal/ioutil" ocispec "github.com/opencontainers/image-spec/specs-go/v1" "github.com/spf13/cobra" diff --git a/internal/ioutil/print.go b/internal/ioutil/print.go index 32d0466b7..c460aee10 100644 --- a/internal/ioutil/print.go +++ b/internal/ioutil/print.go @@ -96,29 +96,31 @@ func ComposeBlobVerificationFailurePrintout(outcomes []*notation.VerificationOut // parseErrorOnVerificationFailure parses error on verification failure. func parseErrorOnVerificationFailure(err error) error { - if err != nil { - var errTrustStore truststore.TrustStoreError - if errors.As(err, &errTrustStore) { - if errors.Is(err, fs.ErrNotExist) { - return fmt.Errorf("%w. Use command 'notation cert add' to create and add trusted certificates to the trust store", errTrustStore) - } else { - return fmt.Errorf("%w. %w", errTrustStore, errTrustStore.InnerError) - } - } + if err == nil { + return nil + } - var errCertificate truststore.CertificateError - if errors.As(err, &errCertificate) { - if errors.Is(err, fs.ErrNotExist) { - return fmt.Errorf("%w. Use command 'notation cert add' to create and add trusted certificates to the trust store", errCertificate) - } else { - return fmt.Errorf("%w. %w", errCertificate, errCertificate.InnerError) - } + var errTrustStore truststore.TrustStoreError + if errors.As(err, &errTrustStore) { + if errors.Is(err, fs.ErrNotExist) { + return fmt.Errorf("%w. Use command 'notation cert add' to create and add trusted certificates to the trust store", errTrustStore) + } else { + return fmt.Errorf("%w. %w", errTrustStore, errTrustStore.InnerError) } + } - var errorVerificationFailed notation.ErrorVerificationFailed - if !errors.As(err, &errorVerificationFailed) { - return fmt.Errorf("signature verification failed: %w", err) + var errCertificate truststore.CertificateError + if errors.As(err, &errCertificate) { + if errors.Is(err, fs.ErrNotExist) { + return fmt.Errorf("%w. Use command 'notation cert add' to create and add trusted certificates to the trust store", errCertificate) + } else { + return fmt.Errorf("%w. %w", errCertificate, errCertificate.InnerError) } } + + var errorVerificationFailed notation.ErrorVerificationFailed + if !errors.As(err, &errorVerificationFailed) { + return fmt.Errorf("signature verification failed: %w", err) + } return nil }