diff --git a/cmd/notation/blob/cmd.go b/cmd/notation/blob/cmd.go new file mode 100644 index 000000000..9c9a55140 --- /dev/null +++ b/cmd/notation/blob/cmd.go @@ -0,0 +1,31 @@ +// 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 provides the implementation of the `notation blob` command +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..635c51c63 --- /dev/null +++ b/cmd/notation/blob/sign.go @@ -0,0 +1,236 @@ +// 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 ( + "context" + "errors" + "fmt" + "net/http" + "os" + "path/filepath" + "time" + + "github.com/notaryproject/notation-core-go/revocation/purpose" + "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 + expiry time.Duration + pluginConfig []string + userMetadata []string + blobPath string + blobMediaType string + signatureDirectory string + tsaServerURL string + tsaRootCertificatePath string + force bool +} + +func signCommand(opts *blobSignOpts) *cobra.Command { + if opts == nil { + opts = &blobSignOpts{} + } + 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: + 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: "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 { + 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 { + // 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) + }, + } + 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.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") + command.Flags().BoolVar(&opts.force, "force", false, "override the existing signature file, never prompt") + command.MarkFlagsRequiredTogether("timestamp-url", "timestamp-root-cert") + return command +} + +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 { + return err + } + blobOpts, err := prepareBlobSigningOpts(ctx, cmdOpts) + if err != nil { + return err + } + blobFile, err := os.Open(cmdOpts.blobPath) + if err != nil { + return err + } + defer blobFile.Close() + + // core process + sig, _, err := notation.SignBlob(ctx, blobSigner, blobFile, blobOpts) + if err != nil { + return err + } + signaturePath := signatureFilepath(cmdOpts.signatureDirectory, cmdOpts.blobPath, cmdOpts.SignatureFormat) + logger.Infof("Writing signature to file %s", signaturePath) + + // 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 to file: %w", err) + } + fmt.Printf("Successfully signed %s\n ", cmdOpts.blobPath) + fmt.Printf("Signature file written to %s\n", signaturePath) + return nil +} + +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 + } + 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 + } + signBlobOpts := notation.SignBlobOptions{ + SignerSignOptions: notation.SignerSignOptions{ + SignatureMediaType: mediaType, + ExpiryDuration: opts.expiry, + PluginConfig: pluginConfig, + }, + 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) + } + signBlobOpts.TSARootCAs, err = nx509.NewRootCertPool(opts.tsaRootCertificatePath) + if err != nil { + return notation.SignBlobOptions{}, err + } + 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 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) + 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..78f74e972 --- /dev/null +++ b/cmd/notation/blob/sign_test.go @@ -0,0 +1,313 @@ +// 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, + }, + signatureDirectory: ".", + blobMediaType: "application/octet-stream", + } + 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, + signatureDirectory: ".", + blobMediaType: "application/octet-stream", + } + 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"}, + signatureDirectory: ".", + blobMediaType: "application/octet-stream", + } + 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, + }, + signatureDirectory: ".", + blobMediaType: "application/octet-stream", + } + 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, + }, + signatureDirectory: ".", + blobMediaType: "application/octet-stream", + } + 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, + }, + signatureDirectory: ".", + blobMediaType: "application/octet-stream", + } + 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, + }, + signatureDirectory: ".", + blobMediaType: "application/octet-stream", + } + 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, + }, + signatureDirectory: ".", + blobMediaType: "application/octet-stream", + } + 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, + }, + signatureDirectory: ".", + blobMediaType: "application/octet-stream", + } + 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/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/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/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/go.mod b/go.mod index 042764960..888434646 100644 --- a/go.mod +++ b/go.mod @@ -3,8 +3,8 @@ module github.com/notaryproject/notation go 1.23 require ( - github.com/notaryproject/notation-core-go v1.2.0-rc.1.0.20241129024749-95d89543c9f9 - github.com/notaryproject/notation-go v1.2.0-beta.1.0.20241202020354-95bac0082974 + github.com/notaryproject/notation-core-go v1.2.0-rc.2 + github.com/notaryproject/notation-go v1.2.0-beta.1.0.20250107003620-26ce0894a624 github.com/notaryproject/tspclient-go v1.0.0-rc.1 github.com/opencontainers/go-digest v1.0.0 github.com/opencontainers/image-spec v1.1.0 @@ -18,8 +18,8 @@ require ( require ( github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 // indirect github.com/fxamacker/cbor/v2 v2.7.0 // indirect - github.com/go-asn1-ber/asn1-ber v1.5.5 // indirect - github.com/go-ldap/ldap/v3 v3.4.8 // indirect + github.com/go-asn1-ber/asn1-ber v1.5.7 // indirect + github.com/go-ldap/ldap/v3 v3.4.10 // indirect github.com/golang-jwt/jwt/v4 v4.5.1 // indirect github.com/google/uuid v1.6.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect @@ -28,6 +28,6 @@ require ( github.com/x448/float16 v0.8.4 // indirect golang.org/x/crypto v0.31.0 // indirect golang.org/x/mod v0.22.0 // indirect - golang.org/x/sync v0.6.0 // indirect + golang.org/x/sync v0.10.0 // indirect golang.org/x/sys v0.28.0 // indirect ) diff --git a/go.sum b/go.sum index 653841d30..34a5fddac 100644 --- a/go.sum +++ b/go.sum @@ -8,12 +8,13 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E= github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= -github.com/go-asn1-ber/asn1-ber v1.5.5 h1:MNHlNMBDgEKD4TcKr36vQN68BA00aDfjIt3/bD50WnA= -github.com/go-asn1-ber/asn1-ber v1.5.5/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0= -github.com/go-ldap/ldap/v3 v3.4.8 h1:loKJyspcRezt2Q3ZRMq2p/0v8iOurlmeXDPw6fikSvQ= -github.com/go-ldap/ldap/v3 v3.4.8/go.mod h1:qS3Sjlu76eHfHGpUdWkAXQTw4beih+cHsco2jXlIXrk= +github.com/go-asn1-ber/asn1-ber v1.5.7 h1:DTX+lbVTWaTw1hQ+PbZPlnDZPEIs0SS/GCZAl535dDk= +github.com/go-asn1-ber/asn1-ber v1.5.7/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0= +github.com/go-ldap/ldap/v3 v3.4.10 h1:ot/iwPOhfpNVgB1o+AVXljizWZ9JTp7YF5oeyONmcJU= +github.com/go-ldap/ldap/v3 v3.4.10/go.mod h1:JXh4Uxgi40P6E9rdsYqpUtbW46D9UTjJ9QSwGRznplY= github.com/golang-jwt/jwt/v4 v4.5.1 h1:JdqV9zKUdtaa9gdPlywC3aeoEsR681PlKC+4F5gQgeo= github.com/golang-jwt/jwt/v4 v4.5.1/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4= @@ -35,10 +36,10 @@ github.com/jcmturner/gokrb5/v8 v8.4.4 h1:x1Sv4HaTpepFkXbt2IkL29DXRf8sOfZXo8eRKh6 github.com/jcmturner/gokrb5/v8 v8.4.4/go.mod h1:1btQEpgT6k+unzCwX1KdWMEwPPkkgBtP+F6aCACiMrs= github.com/jcmturner/rpc/v2 v2.0.3 h1:7FXXj8Ti1IaVFpSAziCZWNzbNuZmnvw/i6CqLNdWfZY= github.com/jcmturner/rpc/v2 v2.0.3/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJki3ELg/Hc= -github.com/notaryproject/notation-core-go v1.2.0-rc.1.0.20241129024749-95d89543c9f9 h1:FURo9xpGLKmghWCcWypCPQTlcOGKxzayeXacGfb8WUU= -github.com/notaryproject/notation-core-go v1.2.0-rc.1.0.20241129024749-95d89543c9f9/go.mod h1:Umjn4NKGmuHpVffMgKVcUnArNG3Qtd3duKYpPILUBg4= -github.com/notaryproject/notation-go v1.2.0-beta.1.0.20241202020354-95bac0082974 h1:EQ9DC25U7hWbBIOlwINxPhr9QEyixg1/Fo5ZZW+3JSU= -github.com/notaryproject/notation-go v1.2.0-beta.1.0.20241202020354-95bac0082974/go.mod h1:6a3/g7yD/8dxxBpimzUWthH8DLBrzHs4RTzdz9CALvw= +github.com/notaryproject/notation-core-go v1.2.0-rc.2 h1:0jOItalNwBNUhyuc5PPHQxO3jIZ5xRYq+IvRMQXNbuE= +github.com/notaryproject/notation-core-go v1.2.0-rc.2/go.mod h1:7aIcavfywFvBQoYyfVFJB501kt7Etqyubrt5mhJBG2c= +github.com/notaryproject/notation-go v1.2.0-beta.1.0.20250107003620-26ce0894a624 h1:JCJ+64H1A/aYhNaUak+1DV4dY2uL3L5GFMRLzrh9tDM= +github.com/notaryproject/notation-go v1.2.0-beta.1.0.20250107003620-26ce0894a624/go.mod h1:1QaHYG/UOeAYhfLBipsSxquu3BheRm7a+5RODcc5nQg= github.com/notaryproject/notation-plugin-framework-go v1.0.0 h1:6Qzr7DGXoCgXEQN+1gTZWuJAZvxh3p8Lryjn5FaLzi4= github.com/notaryproject/notation-plugin-framework-go v1.0.0/go.mod h1:RqWSrTOtEASCrGOEffq0n8pSg2KOgKYiWqFWczRSics= github.com/notaryproject/tspclient-go v1.0.0-rc.1 h1:KcHxlqg6Adt4kzGLw012i0YMLlwGwToiR129c6IQ7Ys= @@ -73,12 +74,16 @@ github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5t golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= +golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= -golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= +golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U= golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4= golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -88,14 +93,19 @@ golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= +golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= -golang.org/x/net v0.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc= -golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= +golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= +golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I= +golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= +golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= +golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -104,16 +114,19 @@ golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= +golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= -golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58= +golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= golang.org/x/term v0.27.0 h1:WP60Sv1nlK1T6SupCHbXzSaN0b9wUmsPoRS9b61A23Q= golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -121,11 +134,16 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= +golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/internal/cmd/signer.go b/internal/cmd/signer.go index 39edc6fda..c0c042026 100644 --- a/internal/cmd/signer.go +++ b/internal/cmd/signer.go @@ -24,8 +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) { +// Signer is embedded with notation.BlobSigner and notation.Signer. +type Signer interface { + notation.BlobSigner + notation.Signer +} + +// 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 @@ -34,7 +40,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 @@ -44,8 +50,9 @@ func GetSigner(ctx context.Context, opts *SignerFlagOpts) (notation.Signer, erro return nil, err } if key.X509KeyPair != nil { - return signer.NewFromFiles(key.X509KeyPair.KeyPath, key.X509KeyPair.CertificatePath) + 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 { @@ -54,7 +61,7 @@ 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") } diff --git a/internal/cmd/signer_test.go b/internal/cmd/signer_test.go new file mode 100644 index 000000000..f41b14e0c --- /dev/null +++ b/internal/cmd/signer_test.go @@ -0,0 +1,217 @@ +// Copyright The Notary Project Authors. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package cmd + +import ( + "context" + "runtime" + "testing" + + "github.com/notaryproject/notation-go" + "github.com/notaryproject/notation-go/dir" + "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") + } +} + +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 = GetSigner(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") + } + + 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 = GetSigner(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") + } +} + +func TestGetSignerFailed(t *testing.T) { + ctx := context.Background() + + defer func(oldLibexeDir, oldConfigDir string) { + dir.UserLibexecDir = oldLibexeDir + 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") + } + + 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 := GetSigner(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 := GetSigner(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 := GetSigner(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 := GetSigner(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 := GetSigner(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 := GetSigner(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/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 +} diff --git a/specs/commandline/blob.md b/specs/commandline/blob.md index 3599eb9a9..532d633f7 100644 --- a/specs/commandline/blob.md +++ b/specs/commandline/blob.md @@ -52,16 +52,16 @@ 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] 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 @@ -70,24 +70,29 @@ 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 + signature format + ".sig" as the file extension. 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. +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] Flags: - --signature-directory string optional path 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 @@ -169,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 diff --git a/test/e2e/go.mod b/test/e2e/go.mod index ad0fadc78..0829a1938 100644 --- a/test/e2e/go.mod +++ b/test/e2e/go.mod @@ -3,8 +3,8 @@ module github.com/notaryproject/notation/test/e2e go 1.23 require ( - github.com/notaryproject/notation-core-go v1.2.0-rc.1.0.20241129024749-95d89543c9f9 - github.com/notaryproject/notation-go v1.2.0-beta.1.0.20241202020354-95bac0082974 + github.com/notaryproject/notation-core-go v1.2.0-rc.2 + github.com/notaryproject/notation-go v1.2.0-beta.1.0.20250107003620-26ce0894a624 github.com/onsi/ginkgo/v2 v2.22.1 github.com/onsi/gomega v1.36.2 github.com/opencontainers/image-spec v1.1.0 @@ -17,7 +17,7 @@ require ( github.com/go-task/slim-sprig/v3 v3.0.0 // indirect github.com/google/go-cmp v0.6.0 // indirect github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad // indirect - github.com/notaryproject/tspclient-go v0.2.1-0.20241030015323-90a141e7525c // indirect + github.com/notaryproject/tspclient-go v1.0.0-rc.1 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect github.com/veraison/go-cose v1.3.0 // indirect github.com/x448/float16 v0.8.4 // indirect diff --git a/test/e2e/go.sum b/test/e2e/go.sum index a430a03ac..ce903a174 100644 --- a/test/e2e/go.sum +++ b/test/e2e/go.sum @@ -10,12 +10,12 @@ github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad h1:a6HEuzUHeKH6hwfN/ZoQgRgVIWFJljSWa/zetS2WTvg= github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144= -github.com/notaryproject/notation-core-go v1.2.0-rc.1.0.20241129024749-95d89543c9f9 h1:FURo9xpGLKmghWCcWypCPQTlcOGKxzayeXacGfb8WUU= -github.com/notaryproject/notation-core-go v1.2.0-rc.1.0.20241129024749-95d89543c9f9/go.mod h1:Umjn4NKGmuHpVffMgKVcUnArNG3Qtd3duKYpPILUBg4= -github.com/notaryproject/notation-go v1.2.0-beta.1.0.20241202020354-95bac0082974 h1:EQ9DC25U7hWbBIOlwINxPhr9QEyixg1/Fo5ZZW+3JSU= -github.com/notaryproject/notation-go v1.2.0-beta.1.0.20241202020354-95bac0082974/go.mod h1:6a3/g7yD/8dxxBpimzUWthH8DLBrzHs4RTzdz9CALvw= -github.com/notaryproject/tspclient-go v0.2.1-0.20241030015323-90a141e7525c h1:bX6gGxFw9+DShmYTgbD+vr6neF1SoXIMUU2fDgdLsfA= -github.com/notaryproject/tspclient-go v0.2.1-0.20241030015323-90a141e7525c/go.mod h1:LGyA/6Kwd2FlM0uk8Vc5il3j0CddbWSHBj/4kxQDbjs= +github.com/notaryproject/notation-core-go v1.2.0-rc.2 h1:0jOItalNwBNUhyuc5PPHQxO3jIZ5xRYq+IvRMQXNbuE= +github.com/notaryproject/notation-core-go v1.2.0-rc.2/go.mod h1:7aIcavfywFvBQoYyfVFJB501kt7Etqyubrt5mhJBG2c= +github.com/notaryproject/notation-go v1.2.0-beta.1.0.20250107003620-26ce0894a624 h1:JCJ+64H1A/aYhNaUak+1DV4dY2uL3L5GFMRLzrh9tDM= +github.com/notaryproject/notation-go v1.2.0-beta.1.0.20250107003620-26ce0894a624/go.mod h1:1QaHYG/UOeAYhfLBipsSxquu3BheRm7a+5RODcc5nQg= +github.com/notaryproject/tspclient-go v1.0.0-rc.1 h1:KcHxlqg6Adt4kzGLw012i0YMLlwGwToiR129c6IQ7Ys= +github.com/notaryproject/tspclient-go v1.0.0-rc.1/go.mod h1:LGyA/6Kwd2FlM0uk8Vc5il3j0CddbWSHBj/4kxQDbjs= github.com/onsi/ginkgo/v2 v2.22.1 h1:QW7tbJAUDyVDVOM5dFa7qaybo+CRfR7bemlQUN6Z8aM= github.com/onsi/ginkgo/v2 v2.22.1/go.mod h1:S6aTpoRsSq2cZOd+pssHAlKW/Q/jZt6cPrPlnj4a1xM= github.com/onsi/gomega v1.36.2 h1:koNYke6TVk6ZmnyHrCXba/T/MoLBXFjeC1PtvYgw0A8= diff --git a/test/e2e/internal/notation/host.go b/test/e2e/internal/notation/host.go index 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/internal/utils/host.go b/test/e2e/internal/utils/host.go index 605032767..3c21805b3 100644 --- a/test/e2e/internal/utils/host.go +++ b/test/e2e/internal/utils/host.go @@ -22,9 +22,8 @@ import ( // VirtualHost is a virtualized host machine isolated by environment variable. type VirtualHost struct { Executor *ExecOpts - - userDir string - env map[string]string + userDir string + env map[string]string } // NewVirtualHost creates a temporary user-level directory and updates diff --git a/test/e2e/plugin/go.mod b/test/e2e/plugin/go.mod index 51bd4dd06..b48fc08cb 100644 --- a/test/e2e/plugin/go.mod +++ b/test/e2e/plugin/go.mod @@ -4,8 +4,8 @@ go 1.23 require ( github.com/golang-jwt/jwt v3.2.2+incompatible - github.com/notaryproject/notation-core-go v1.2.0-rc.1.0.20241129024749-95d89543c9f9 - github.com/notaryproject/notation-go v1.2.0-beta.1.0.20241202020354-95bac0082974 + github.com/notaryproject/notation-core-go v1.2.0-rc.2 + github.com/notaryproject/notation-go v1.2.0-beta.1.0.20250107003620-26ce0894a624 github.com/notaryproject/notation-plugin-framework-go v1.0.0 github.com/spf13/cobra v1.8.1 ) @@ -13,19 +13,19 @@ require ( require ( github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 // indirect github.com/fxamacker/cbor/v2 v2.7.0 // indirect - github.com/go-asn1-ber/asn1-ber v1.5.5 // indirect - github.com/go-ldap/ldap/v3 v3.4.8 // indirect + github.com/go-asn1-ber/asn1-ber v1.5.7 // indirect + github.com/go-ldap/ldap/v3 v3.4.10 // indirect github.com/golang-jwt/jwt/v4 v4.5.1 // indirect github.com/google/uuid v1.6.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect - github.com/notaryproject/tspclient-go v0.2.1-0.20241030015323-90a141e7525c // indirect + github.com/notaryproject/tspclient-go v1.0.0-rc.1 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.1.0 // indirect github.com/spf13/pflag v1.0.5 // indirect github.com/veraison/go-cose v1.3.0 // indirect github.com/x448/float16 v0.8.4 // indirect - golang.org/x/crypto v0.29.0 // indirect + golang.org/x/crypto v0.31.0 // indirect golang.org/x/mod v0.22.0 // indirect - golang.org/x/sync v0.6.0 // indirect + golang.org/x/sync v0.10.0 // indirect oras.land/oras-go/v2 v2.5.0 // indirect ) diff --git a/test/e2e/plugin/go.sum b/test/e2e/plugin/go.sum index 4dccc8be0..6c88aef63 100644 --- a/test/e2e/plugin/go.sum +++ b/test/e2e/plugin/go.sum @@ -8,14 +8,15 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E= github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= -github.com/go-asn1-ber/asn1-ber v1.5.5 h1:MNHlNMBDgEKD4TcKr36vQN68BA00aDfjIt3/bD50WnA= -github.com/go-asn1-ber/asn1-ber v1.5.5/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0= -github.com/go-ldap/ldap/v3 v3.4.8 h1:loKJyspcRezt2Q3ZRMq2p/0v8iOurlmeXDPw6fikSvQ= -github.com/go-ldap/ldap/v3 v3.4.8/go.mod h1:qS3Sjlu76eHfHGpUdWkAXQTw4beih+cHsco2jXlIXrk= +github.com/go-asn1-ber/asn1-ber v1.5.7 h1:DTX+lbVTWaTw1hQ+PbZPlnDZPEIs0SS/GCZAl535dDk= +github.com/go-asn1-ber/asn1-ber v1.5.7/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0= +github.com/go-ldap/ldap/v3 v3.4.10 h1:ot/iwPOhfpNVgB1o+AVXljizWZ9JTp7YF5oeyONmcJU= +github.com/go-ldap/ldap/v3 v3.4.10/go.mod h1:JXh4Uxgi40P6E9rdsYqpUtbW46D9UTjJ9QSwGRznplY= github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY= github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= github.com/golang-jwt/jwt/v4 v4.5.1 h1:JdqV9zKUdtaa9gdPlywC3aeoEsR681PlKC+4F5gQgeo= github.com/golang-jwt/jwt/v4 v4.5.1/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4= @@ -37,14 +38,14 @@ github.com/jcmturner/gokrb5/v8 v8.4.4 h1:x1Sv4HaTpepFkXbt2IkL29DXRf8sOfZXo8eRKh6 github.com/jcmturner/gokrb5/v8 v8.4.4/go.mod h1:1btQEpgT6k+unzCwX1KdWMEwPPkkgBtP+F6aCACiMrs= github.com/jcmturner/rpc/v2 v2.0.3 h1:7FXXj8Ti1IaVFpSAziCZWNzbNuZmnvw/i6CqLNdWfZY= github.com/jcmturner/rpc/v2 v2.0.3/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJki3ELg/Hc= -github.com/notaryproject/notation-core-go v1.2.0-rc.1.0.20241129024749-95d89543c9f9 h1:FURo9xpGLKmghWCcWypCPQTlcOGKxzayeXacGfb8WUU= -github.com/notaryproject/notation-core-go v1.2.0-rc.1.0.20241129024749-95d89543c9f9/go.mod h1:Umjn4NKGmuHpVffMgKVcUnArNG3Qtd3duKYpPILUBg4= -github.com/notaryproject/notation-go v1.2.0-beta.1.0.20241202020354-95bac0082974 h1:EQ9DC25U7hWbBIOlwINxPhr9QEyixg1/Fo5ZZW+3JSU= -github.com/notaryproject/notation-go v1.2.0-beta.1.0.20241202020354-95bac0082974/go.mod h1:6a3/g7yD/8dxxBpimzUWthH8DLBrzHs4RTzdz9CALvw= +github.com/notaryproject/notation-core-go v1.2.0-rc.2 h1:0jOItalNwBNUhyuc5PPHQxO3jIZ5xRYq+IvRMQXNbuE= +github.com/notaryproject/notation-core-go v1.2.0-rc.2/go.mod h1:7aIcavfywFvBQoYyfVFJB501kt7Etqyubrt5mhJBG2c= +github.com/notaryproject/notation-go v1.2.0-beta.1.0.20250107003620-26ce0894a624 h1:JCJ+64H1A/aYhNaUak+1DV4dY2uL3L5GFMRLzrh9tDM= +github.com/notaryproject/notation-go v1.2.0-beta.1.0.20250107003620-26ce0894a624/go.mod h1:1QaHYG/UOeAYhfLBipsSxquu3BheRm7a+5RODcc5nQg= github.com/notaryproject/notation-plugin-framework-go v1.0.0 h1:6Qzr7DGXoCgXEQN+1gTZWuJAZvxh3p8Lryjn5FaLzi4= github.com/notaryproject/notation-plugin-framework-go v1.0.0/go.mod h1:RqWSrTOtEASCrGOEffq0n8pSg2KOgKYiWqFWczRSics= -github.com/notaryproject/tspclient-go v0.2.1-0.20241030015323-90a141e7525c h1:bX6gGxFw9+DShmYTgbD+vr6neF1SoXIMUU2fDgdLsfA= -github.com/notaryproject/tspclient-go v0.2.1-0.20241030015323-90a141e7525c/go.mod h1:LGyA/6Kwd2FlM0uk8Vc5il3j0CddbWSHBj/4kxQDbjs= +github.com/notaryproject/tspclient-go v1.0.0-rc.1 h1:KcHxlqg6Adt4kzGLw012i0YMLlwGwToiR129c6IQ7Ys= +github.com/notaryproject/tspclient-go v1.0.0-rc.1/go.mod h1:LGyA/6Kwd2FlM0uk8Vc5il3j0CddbWSHBj/4kxQDbjs= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug= @@ -72,12 +73,16 @@ github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5t golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= +golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= -golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= -golang.org/x/crypto v0.29.0 h1:L5SG1JTTXupVV3n6sUqMTeWbjAyfPwoda2DLX8J8FrQ= -golang.org/x/crypto v0.29.0/go.mod h1:+F4F4N5hv6v38hfeYwTdx20oUvLLc+QfrE9Ax9HtgRg= +golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= +golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U= +golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4= golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -87,14 +92,19 @@ golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= +golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= -golang.org/x/net v0.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc= -golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= +golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= +golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I= +golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= +golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= +golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -102,24 +112,34 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= +golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= -golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58= +golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= +golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= +golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/test/e2e/run.sh b/test/e2e/run.sh index 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..3e5ab269c --- /dev/null +++ b/test/e2e/suite/command/blob/sign.go @@ -0,0 +1,244 @@ +// 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" + "os" + "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() { + // Success cases + It("with blob sign", func() { + HostWithBlob(BaseOptions(), func(notation *utils.ExecOpts, blobPath string, vhost *utils.VirtualHost) { + notation.WithWorkDir(vhost.AbsolutePath()).Exec("blob", "sign", blobPath). + MatchKeyWords(SignSuccessfully). + MatchKeyWords("Signature file written to") + }) + }) + + It("with COSE format", func() { + HostWithBlob(BaseOptions(), func(notation *utils.ExecOpts, blobPath string, vhost *utils.VirtualHost) { + notation.WithWorkDir(vhost.AbsolutePath()).Exec("blob", "sign", "--signature-format", "cose", blobPath). + MatchKeyWords(SignSuccessfully). + MatchKeyWords("Signature file written to") + }) + }) + + It("with specified media-type", func() { + HostWithBlob(BaseOptions(), func(notation *utils.ExecOpts, blobPath string, vhost *utils.VirtualHost) { + notation.WithWorkDir(vhost.AbsolutePath()).Exec("blob", "sign", "--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" + notation.WithWorkDir(vhost.AbsolutePath()).Exec("cert", "generate-test", keyName). + MatchKeyWords(fmt.Sprintf("notation/localkeys/%s.crt", keyName)) + + notation.WithWorkDir(vhost.AbsolutePath()).Exec("blob", "sign", "--key", keyName, blobPath). + MatchKeyWords(SignSuccessfully). + MatchKeyWords("Signature file written to") + }) + }) + + It("with expiry in 24h", func() { + HostWithBlob(BaseOptions(), func(notation *utils.ExecOpts, blobPath string, vhost *utils.VirtualHost) { + notation.WithWorkDir(vhost.AbsolutePath()).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", vhost.AbsolutePath(), blobPath). + MatchKeyWords(SignSuccessfully). + MatchKeyWords(fmt.Sprintf("Signature file written to %s", vhost.AbsolutePath("blobFile.jws.sig"))) + }) + }) + + It("with user metadata", func() { + HostWithBlob(BaseOptions(), func(notation *utils.ExecOpts, blobPath string, vhost *utils.VirtualHost) { + notation.WithWorkDir(vhost.AbsolutePath()).Exec("blob", "sign", "--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.WithWorkDir(vhost.AbsolutePath()).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 --force flag", func() { + HostWithBlob(BaseOptions(), func(notation *utils.ExecOpts, blobPath string, vhost *utils.VirtualHost) { + sigDir := vhost.AbsolutePath() + notation.Exec("blob", "sign", "--signature-directory", sigDir, blobPath). + MatchKeyWords(SignSuccessfully). + MatchKeyWords(fmt.Sprintf("Signature file written to %s", vhost.AbsolutePath("blobFile.jws.sig"))) + + notation.Exec("blob", "sign", "--force", "--signature-directory", sigDir, blobPath). + MatchKeyWords(SignSuccessfully). + MatchKeyWords(fmt.Sprintf("Signature file written to %s", vhost.AbsolutePath("blobFile.jws.sig"))) + }) + }) + + // 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) { + noPermissionBlobPath := vhost.AbsolutePath("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(noPermissionBlobPath, 0700) + + notation.ExpectFailure().Exec("blob", "sign", noPermissionBlobPath). + MatchErrKeyWords("permission denied") + }) + }) + + It("with no permission to write the signature file", func() { + HostWithBlob(BaseOptions(), func(notation *utils.ExecOpts, blobPath string, vhost *utils.VirtualHost) { + sigDir := vhost.AbsolutePath("signature") + if err := os.MkdirAll(sigDir, 0000); err != nil { + Fail(err.Error()) + } + defer os.Chmod(sigDir, 0700) + + notation.ExpectFailure().Exec("blob", "sign", "--signature-directory", sigDir, 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). + 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\"") + }) + }) + + 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..d9cefd445 100644 --- a/test/e2e/suite/command/sign.go +++ b/test/e2e/suite/command/sign.go @@ -296,9 +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\""). - MatchErrKeyWords("server misbehaving") + 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\"") }) }) 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