From 1619d18a1e5f0b0336cfd73ba25d84ad7048c2a8 Mon Sep 17 00:00:00 2001 From: Patrick Zheng Date: Tue, 24 Dec 2024 17:05:18 +0800 Subject: [PATCH 01/21] 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/21] 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/21] 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/21] 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/21] 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/21] 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/21] 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/21] 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/21] 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/21] 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/21] 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/21] 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/21] 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/21] 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/21] 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/21] 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/21] 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/21] 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 9777ada18a3f9b257a46aa85ed096161f51e412d Mon Sep 17 00:00:00 2001 From: Patrick Zheng Date: Wed, 8 Jan 2025 14:43:19 +0800 Subject: [PATCH 19/21] update Signed-off-by: Patrick Zheng --- cmd/notation/blob/sign.go | 26 +++----------------------- cmd/notation/sign.go | 23 +---------------------- internal/x509/cert.go | 29 +++++++++++++++++++++++++++++ 3 files changed, 33 insertions(+), 45 deletions(-) diff --git a/cmd/notation/blob/sign.go b/cmd/notation/blob/sign.go index 2ea700196..635c51c63 100644 --- a/cmd/notation/blob/sign.go +++ b/cmd/notation/blob/sign.go @@ -15,7 +15,6 @@ package blob import ( "context" - "crypto/x509" "errors" "fmt" "net/http" @@ -24,7 +23,6 @@ import ( "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" @@ -138,6 +136,7 @@ Example - Sign a blob artifact with timestamping: func runBlobSign(command *cobra.Command, cmdOpts *blobSignOpts) error { // set log level ctx := cmdOpts.LoggingFlagOpts.InitializeLogger(command.Context()) + logger := log.GetLogger(ctx) blobSigner, err := cmd.GetSigner(ctx, &cmdOpts.SignerFlagOpts) if err != nil { @@ -159,7 +158,7 @@ func runBlobSign(command *cobra.Command, cmdOpts *blobSignOpts) error { return err } signaturePath := signatureFilepath(cmdOpts.signatureDirectory, cmdOpts.blobPath, cmdOpts.SignatureFormat) - fmt.Printf("Writing signature to file %s\n", signaturePath) + logger.Infof("Writing signature to file %s", signaturePath) // optional confirmation if !cmdOpts.force { @@ -216,29 +215,10 @@ func prepareBlobSigningOpts(ctx context.Context, opts *blobSignOpts) (notation.S if err != nil { return notation.SignBlobOptions{}, fmt.Errorf("cannot get http timestamper for timestamping: %w", err) } - - rootCerts, err := corex509.ReadCertificateFile(opts.tsaRootCertificatePath) + signBlobOpts.TSARootCAs, err = nx509.NewRootCertPool(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) diff --git a/cmd/notation/sign.go b/cmd/notation/sign.go index 60bca1509..441d22947 100644 --- a/cmd/notation/sign.go +++ b/cmd/notation/sign.go @@ -15,7 +15,6 @@ package main import ( "context" - "crypto/x509" "errors" "fmt" "net/http" @@ -24,7 +23,6 @@ import ( "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/experimental" @@ -230,29 +228,10 @@ func prepareSigningOpts(ctx context.Context, opts *signOpts) (notation.SignOptio if err != nil { return notation.SignOptions{}, fmt.Errorf("cannot get http timestamper for timestamping: %w", err) } - - rootCerts, err := corex509.ReadCertificateFile(opts.tsaRootCertificatePath) + signOpts.TSARootCAs, err = nx509.NewRootCertPool(opts.tsaRootCertificatePath) if err != nil { return notation.SignOptions{}, err } - if len(rootCerts) == 0 { - return notation.SignOptions{}, fmt.Errorf("cannot find any certificate from %q. Expecting single x509 root certificate in PEM or DER format from the file", opts.tsaRootCertificatePath) - } - if len(rootCerts) > 1 { - return notation.SignOptions{}, fmt.Errorf("found more than one certificates from %q. Expecting single x509 root certificate in PEM or DER format from the file", opts.tsaRootCertificatePath) - } - tsaRootCert := rootCerts[0] - isRoot, err := nx509.IsRootCertificate(tsaRootCert) - if err != nil { - return notation.SignOptions{}, fmt.Errorf("failed to check root certificate with error: %w", err) - } - if !isRoot { - return notation.SignOptions{}, fmt.Errorf("certificate from %q is not a root certificate. Expecting single x509 root certificate in PEM or DER format from the file", opts.tsaRootCertificatePath) - - } - rootCAs := x509.NewCertPool() - rootCAs.AddCert(tsaRootCert) - signOpts.TSARootCAs = rootCAs tsaRevocationValidator, err := clirev.NewRevocationValidator(ctx, purpose.Timestamping) if err != nil { return notation.SignOptions{}, fmt.Errorf("failed to create timestamping revocation validator: %w", err) diff --git a/internal/x509/cert.go b/internal/x509/cert.go index c72ef76c4..252b53545 100644 --- a/internal/x509/cert.go +++ b/internal/x509/cert.go @@ -16,6 +16,9 @@ package x509 import ( "bytes" "crypto/x509" + "fmt" + + corex509 "github.com/notaryproject/notation-core-go/x509" ) // IsRootCertificate returns true if cert is a root certificate. @@ -26,3 +29,29 @@ func IsRootCertificate(cert *x509.Certificate) (bool, error) { } return bytes.Equal(cert.RawSubject, cert.RawIssuer), nil } + +// NewRootCertPool returns a new x509 CertPool containing the root certificate +// from rootCertificatePath. +func NewRootCertPool(rootCertificatePath string) (*x509.CertPool, error) { + rootCerts, err := corex509.ReadCertificateFile(rootCertificatePath) + if err != nil { + return nil, err + } + if len(rootCerts) == 0 { + return nil, fmt.Errorf("cannot find any certificate from %q. Expecting single x509 root certificate in PEM or DER format from the file", rootCertificatePath) + } + if len(rootCerts) > 1 { + return nil, fmt.Errorf("found more than one certificates from %q. Expecting single x509 root certificate in PEM or DER format from the file", rootCertificatePath) + } + rootCert := rootCerts[0] + isRoot, err := IsRootCertificate(rootCert) + if err != nil { + return nil, fmt.Errorf("failed to check root certificate with error: %w", err) + } + if !isRoot { + return nil, fmt.Errorf("certificate from %q is not a root certificate. Expecting single x509 root certificate in PEM or DER format from the file", rootCertificatePath) + } + rootCAs := x509.NewCertPool() + rootCAs.AddCert(rootCert) + return rootCAs, nil +} From b858af6b6e727888fdbdc2e5a2f25ec9fba51a04 Mon Sep 17 00:00:00 2001 From: Patrick Zheng Date: Wed, 8 Jan 2025 15:41:33 +0800 Subject: [PATCH 20/21] bump up dependencies Signed-off-by: Patrick Zheng --- go.mod | 10 ++++---- go.sum | 46 +++++++++++++++++++++++----------- test/e2e/go.mod | 6 ++--- test/e2e/go.sum | 12 ++++----- test/e2e/plugin/go.mod | 14 +++++------ test/e2e/plugin/go.sum | 56 ++++++++++++++++++++++++++++-------------- 6 files changed, 91 insertions(+), 53 deletions(-) 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/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/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= From ac5f3b7ded6674eb2c440e5ea239224ebed9952a Mon Sep 17 00:00:00 2001 From: Patrick Zheng Date: Thu, 9 Jan 2025 16:59:55 +0800 Subject: [PATCH 21/21] update Signed-off-by: Patrick Zheng --- cmd/notation/blob/cmd.go | 1 + test/e2e/suite/command/blob/sign.go | 14 +++++++++++--- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/cmd/notation/blob/cmd.go b/cmd/notation/blob/cmd.go index 2883acabf..9c9a55140 100644 --- a/cmd/notation/blob/cmd.go +++ b/cmd/notation/blob/cmd.go @@ -11,6 +11,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +// Package blob provides the implementation of the `notation blob` command package blob import "github.com/spf13/cobra" 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") }) })