diff --git a/cmd/notation/blob/cmd.go b/cmd/notation/blob/cmd.go index 9c9a55140..4b63895a4 100644 --- a/cmd/notation/blob/cmd.go +++ b/cmd/notation/blob/cmd.go @@ -22,10 +22,9 @@ func Cmd() *cobra.Command { Short: "Commands for blob", Long: "Sign, verify, inspect signatures of blob. Configure blob trust policy.", } - command.AddCommand( signCommand(nil), + verifyCommand(nil), ) - return command } diff --git a/cmd/notation/blob/verify.go b/cmd/notation/blob/verify.go new file mode 100644 index 000000000..8509a76c1 --- /dev/null +++ b/cmd/notation/blob/verify.go @@ -0,0 +1,162 @@ +// Copyright The Notary Project Authors. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package blob + +import ( + "errors" + "fmt" + "os" + "path/filepath" + "strings" + + "github.com/notaryproject/notation-core-go/signature/cose" + "github.com/notaryproject/notation-core-go/signature/jws" + "github.com/notaryproject/notation-go" + "github.com/notaryproject/notation/internal/cmd" + "github.com/notaryproject/notation/internal/ioutil" + "github.com/spf13/cobra" +) + +type blobVerifyOpts struct { + cmd.LoggingFlagOpts + blobPath string + signaturePath string + pluginConfig []string + userMetadata []string + policyStatementName string + blobMediaType string +} + +func verifyCommand(opts *blobVerifyOpts) *cobra.Command { + if opts == nil { + opts = &blobVerifyOpts{} + } + longMessage := `Verify a signature associated with a blob. + +Prerequisite: added a certificate into trust store and created a trust policy. + +Example - Verify a signature on a blob artifact: + notation blob verify --signature + +Example - Verify the signature on a blob artifact with user metadata: + notation blob verify --user-metadata --signature + +Example - Verify the signature on a blob artifact with media type: + notation blob verify --media-type --signature + +Example - Verify the signature on a blob artifact using a policy statement name: + notation blob verify --policy-name --signature +` + command := &cobra.Command{ + Use: "verify [flags] --signature ", + Short: "Verify a signature associated with a blob", + Long: longMessage, + Args: func(cmd *cobra.Command, args []string) error { + if len(args) == 0 { + return errors.New("missing path to the blob artifact: use `notation blob verify --help` to see what parameters are required") + } + opts.blobPath = args[0] + return nil + }, + PreRunE: func(cmd *cobra.Command, args []string) error { + if opts.signaturePath == "" { + return errors.New("filepath of the signature cannot be empty") + } + if cmd.Flags().Changed("media-type") && opts.blobMediaType == "" { + return errors.New("--media-type is set but with empty value") + } + return nil + }, + RunE: func(cmd *cobra.Command, args []string) error { + return runVerify(cmd, opts) + }, + } + opts.LoggingFlagOpts.ApplyFlags(command.Flags()) + command.Flags().StringVar(&opts.signaturePath, "signature", "", "filepath of the signature to be verified") + command.Flags().StringArrayVar(&opts.pluginConfig, "plugin-config", nil, "{key}={value} pairs that are passed as it is to a plugin, if the verification is associated with a verification plugin, refer plugin documentation to set appropriate values") + command.Flags().StringVar(&opts.blobMediaType, "media-type", "", "media type of the blob to verify") + command.Flags().StringVar(&opts.policyStatementName, "policy-name", "", "policy name to verify against. If not provided, the global policy is used if exists") + cmd.SetPflagUserMetadata(command.Flags(), &opts.userMetadata, cmd.PflagUserMetadataVerifyUsage) + command.MarkFlagRequired("signature") + return command +} + +func runVerify(command *cobra.Command, cmdOpts *blobVerifyOpts) error { + // set log level + ctx := cmdOpts.LoggingFlagOpts.InitializeLogger(command.Context()) + + // initialize + blobFile, err := os.Open(cmdOpts.blobPath) + if err != nil { + return err + } + defer blobFile.Close() + + signatureBytes, err := os.ReadFile(cmdOpts.signaturePath) + if err != nil { + return err + } + blobVerifier, err := cmd.GetVerifier(ctx, true) + if err != nil { + return err + } + + // set up verification plugin config + pluginConfigs, err := cmd.ParseFlagMap(cmdOpts.pluginConfig, cmd.PflagPluginConfig.Name) + if err != nil { + return err + } + + // set up user metadata + userMetadata, err := cmd.ParseFlagMap(cmdOpts.userMetadata, cmd.PflagUserMetadata.Name) + if err != nil { + return err + } + + signatureMediaType, err := parseSignatureMediaType(cmdOpts.signaturePath) + if err != nil { + return err + } + verifyBlobOpts := notation.VerifyBlobOptions{ + BlobVerifierVerifyOptions: notation.BlobVerifierVerifyOptions{ + SignatureMediaType: signatureMediaType, + PluginConfig: pluginConfigs, + UserMetadata: userMetadata, + TrustPolicyName: cmdOpts.policyStatementName, + }, + ContentMediaType: cmdOpts.blobMediaType, + } + _, outcome, err := notation.VerifyBlob(ctx, blobVerifier, blobFile, signatureBytes, verifyBlobOpts) + outcomes := []*notation.VerificationOutcome{outcome} + err = ioutil.PrintVerificationFailure(outcomes, cmdOpts.blobPath, err, true) + if err != nil { + return err + } + ioutil.PrintVerificationSuccess(outcomes, cmdOpts.blobPath) + return nil +} + +// parseSignatureMediaType returns the media type of the signature file. +// `application/jose+json` and `application/cose` are supported. +func parseSignatureMediaType(signaturePath string) (string, error) { + signatureFileName := filepath.Base(signaturePath) + format := strings.Split(signatureFileName, ".")[1] + switch format { + case "cose": + return cose.MediaTypeEnvelope, nil + case "jws": + return jws.MediaTypeEnvelope, nil + } + return "", fmt.Errorf("unsupported signature format %s", format) +} diff --git a/cmd/notation/blob/verify_test.go b/cmd/notation/blob/verify_test.go new file mode 100644 index 000000000..b3074ccfd --- /dev/null +++ b/cmd/notation/blob/verify_test.go @@ -0,0 +1,73 @@ +// Copyright The Notary Project Authors. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package blob + +import ( + "reflect" + "testing" +) + +func TestVerifyCommand_BasicArgs(t *testing.T) { + opts := &blobVerifyOpts{} + command := verifyCommand(opts) + expected := &blobVerifyOpts{ + blobPath: "blob_path", + signaturePath: "sig_path", + } + if err := command.ParseFlags([]string{ + expected.blobPath, + "--signature", expected.signaturePath}); err != nil { + t.Fatalf("Parse Flag failed: %v", err) + } + if err := command.Args(command, command.Flags().Args()); err != nil { + t.Fatalf("Parse args failed: %v", err) + } + if !reflect.DeepEqual(*expected, *opts) { + t.Fatalf("Expect blob verify opts: %v, got: %v", expected, opts) + } +} + +func TestVerifyCommand_MoreArgs(t *testing.T) { + opts := &blobVerifyOpts{} + command := verifyCommand(opts) + expected := &blobVerifyOpts{ + blobPath: "blob_path", + signaturePath: "sig_path", + pluginConfig: []string{"key1=val1", "key2=val2"}, + } + if err := command.ParseFlags([]string{ + expected.blobPath, + "--signature", expected.signaturePath, + "--plugin-config", "key1=val1", + "--plugin-config", "key2=val2", + }); err != nil { + t.Fatalf("Parse Flag failed: %v", err) + } + if err := command.Args(command, command.Flags().Args()); err != nil { + t.Fatalf("Parse args failed: %v", err) + } + if !reflect.DeepEqual(*expected, *opts) { + t.Fatalf("Expect verify opts: %v, got: %v", expected, opts) + } +} + +func TestVerifyCommand_MissingArgs(t *testing.T) { + cmd := verifyCommand(nil) + if err := cmd.ParseFlags(nil); err != nil { + t.Fatalf("Parse Flag failed: %v", err) + } + if err := cmd.Args(cmd, cmd.Flags().Args()); err == nil { + t.Fatal("Parse Args expected error, but ok") + } +} diff --git a/cmd/notation/verify.go b/cmd/notation/verify.go index bfcb36d6f..8cea3dceb 100644 --- a/cmd/notation/verify.go +++ b/cmd/notation/verify.go @@ -14,26 +14,15 @@ package main import ( - "context" "errors" "fmt" - "io/fs" "os" - "reflect" - "github.com/notaryproject/notation-core-go/revocation/purpose" "github.com/notaryproject/notation-go" - "github.com/notaryproject/notation-go/dir" - "github.com/notaryproject/notation-go/plugin" - "github.com/notaryproject/notation-go/verifier" - "github.com/notaryproject/notation-go/verifier/trustpolicy" - "github.com/notaryproject/notation-go/verifier/truststore" "github.com/notaryproject/notation/cmd/notation/internal/experimental" "github.com/notaryproject/notation/internal/cmd" "github.com/notaryproject/notation/internal/ioutil" "github.com/spf13/cobra" - - clirev "github.com/notaryproject/notation/internal/revocation" ) type verifyOpts struct { @@ -117,12 +106,12 @@ func runVerify(command *cobra.Command, opts *verifyOpts) error { ctx := opts.LoggingFlagOpts.InitializeLogger(command.Context()) // initialize - sigVerifier, err := getVerifier(ctx) + sigVerifier, err := cmd.GetVerifier(ctx, false) if err != nil { return err } - // set up verification plugin config. + // set up verification plugin config configs, err := cmd.ParseFlagMap(opts.pluginConfig, cmd.PflagPluginConfig.Name) if err != nil { return err @@ -155,97 +144,10 @@ func runVerify(command *cobra.Command, opts *verifyOpts) error { UserMetadata: userMetadata, } _, outcomes, err := notation.Verify(ctx, sigVerifier, sigRepo, verifyOpts) - err = checkVerificationFailure(outcomes, resolvedRef, err) + err = ioutil.PrintVerificationFailure(outcomes, resolvedRef, err, false) if err != nil { return err } - reportVerificationSuccess(outcomes, resolvedRef) + ioutil.PrintVerificationSuccess(outcomes, resolvedRef) return nil } - -func checkVerificationFailure(outcomes []*notation.VerificationOutcome, printOut string, err error) error { - // write out on failure - if err != nil || len(outcomes) == 0 { - if err != nil { - var errTrustStore truststore.TrustStoreError - if errors.As(err, &errTrustStore) { - if errors.Is(err, fs.ErrNotExist) { - return fmt.Errorf("%w. Use command 'notation cert add' to create and add trusted certificates to the trust store", errTrustStore) - } else { - return fmt.Errorf("%w. %w", errTrustStore, errTrustStore.InnerError) - } - } - - var errCertificate truststore.CertificateError - if errors.As(err, &errCertificate) { - if errors.Is(err, fs.ErrNotExist) { - return fmt.Errorf("%w. Use command 'notation cert add' to create and add trusted certificates to the trust store", errCertificate) - } else { - return fmt.Errorf("%w. %w", errCertificate, errCertificate.InnerError) - } - } - - var errorVerificationFailed notation.ErrorVerificationFailed - if !errors.As(err, &errorVerificationFailed) { - return fmt.Errorf("signature verification failed: %w", err) - } - } - return fmt.Errorf("signature verification failed for all the signatures associated with %s", printOut) - } - return nil -} - -func reportVerificationSuccess(outcomes []*notation.VerificationOutcome, printout string) { - // write out on success - outcome := outcomes[0] - // print out warning for any failed result with logged verification action - for _, result := range outcome.VerificationResults { - if result.Error != nil { - // at this point, the verification action has to be logged and - // it's failed - fmt.Fprintf(os.Stderr, "Warning: %v was set to %q and failed with error: %v\n", result.Type, result.Action, result.Error) - } - } - if reflect.DeepEqual(outcome.VerificationLevel, trustpolicy.LevelSkip) { - fmt.Println("Trust policy is configured to skip signature verification for", printout) - } else { - fmt.Println("Successfully verified signature for", printout) - printMetadataIfPresent(outcome) - } -} - -func printMetadataIfPresent(outcome *notation.VerificationOutcome) { - // the signature envelope is parsed as part of verification. - // since user metadata is only printed on successful verification, - // this error can be ignored - metadata, _ := outcome.UserMetadata() - - if len(metadata) > 0 { - fmt.Println("\nThe artifact was signed with the following user metadata.") - ioutil.PrintMetadataMap(os.Stdout, metadata) - } -} - -func getVerifier(ctx context.Context) (notation.Verifier, error) { - // revocation check - revocationCodeSigningValidator, err := clirev.NewRevocationValidator(ctx, purpose.CodeSigning) - if err != nil { - return nil, err - } - revocationTimestampingValidator, err := clirev.NewRevocationValidator(ctx, purpose.Timestamping) - if err != nil { - return nil, err - } - - // trust policy and trust store - policyDocument, err := trustpolicy.LoadOCIDocument() - if err != nil { - return nil, err - } - x509TrustStore := truststore.NewX509TrustStore(dir.ConfigFS()) - - return verifier.NewVerifierWithOptions(policyDocument, nil, x509TrustStore, plugin.NewCLIManager(dir.PluginFS()), verifier.VerifierOptions{ - RevocationCodeSigningValidator: revocationCodeSigningValidator, - RevocationTimestampingValidator: revocationTimestampingValidator, - }) -} diff --git a/cmd/notation/verify_test.go b/cmd/notation/verify_test.go index b68796436..6ae49b8c8 100644 --- a/cmd/notation/verify_test.go +++ b/cmd/notation/verify_test.go @@ -14,15 +14,8 @@ package main import ( - "context" - "encoding/json" - "os" - "path/filepath" "reflect" "testing" - - "github.com/notaryproject/notation-go/dir" - "github.com/notaryproject/notation-go/verifier/trustpolicy" ) func TestVerifyCommand_BasicArgs(t *testing.T) { @@ -87,50 +80,3 @@ func TestVerifyCommand_MissingArgs(t *testing.T) { t.Fatal("Parse Args expected error, but ok") } } - -func TestGetVerifier(t *testing.T) { - defer func(oldConfiDir, oldCacheDir string) { - dir.UserConfigDir = oldConfiDir - dir.UserCacheDir = oldCacheDir - }(dir.UserConfigDir, dir.UserCacheDir) - - t.Run("success", func(t *testing.T) { - tempRoot := t.TempDir() - dir.UserConfigDir = tempRoot - path := filepath.Join(tempRoot, "trustpolicy.json") - policyJson, _ := json.Marshal(dummyOCIPolicyDocument()) - if err := os.WriteFile(path, policyJson, 0600); err != nil { - t.Fatalf("TestLoadOCIDocument write policy file failed. Error: %v", err) - } - t.Cleanup(func() { os.RemoveAll(tempRoot) }) - - _, err := getVerifier(context.Background()) - if err != nil { - t.Fatal(err) - } - }) - - t.Run("non-existing trust policy", func(t *testing.T) { - dir.UserConfigDir = "/" - expectedErrMsg := "trust policy is not present. To create a trust policy, see: https://notaryproject.dev/docs/quickstart/#create-a-trust-policy" - _, err := getVerifier(context.Background()) - if err == nil || err.Error() != expectedErrMsg { - t.Fatalf("expected %s, but got %s", expectedErrMsg, err) - } - }) -} - -func dummyOCIPolicyDocument() trustpolicy.OCIDocument { - return trustpolicy.OCIDocument{ - Version: "1.0", - TrustPolicies: []trustpolicy.OCITrustPolicy{ - { - Name: "test-statement-name", - RegistryScopes: []string{"registry.acme-rockets.io/software/net-monitor"}, - SignatureVerification: trustpolicy.SignatureVerification{VerificationLevel: "strict"}, - TrustStores: []string{"ca:valid-trust-store", "signingAuthority:valid-trust-store"}, - TrustedIdentities: []string{"x509.subject:CN=Notation Test Root,O=Notary,L=Seattle,ST=WA,C=US"}, - }, - }, - } -} diff --git a/internal/cmd/verifier.go b/internal/cmd/verifier.go new file mode 100644 index 000000000..9ff15cde2 --- /dev/null +++ b/internal/cmd/verifier.go @@ -0,0 +1,70 @@ +// Copyright The Notary Project Authors. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package cmd + +import ( + "context" + + "github.com/notaryproject/notation-core-go/revocation/purpose" + "github.com/notaryproject/notation-go" + "github.com/notaryproject/notation-go/dir" + "github.com/notaryproject/notation-go/plugin" + "github.com/notaryproject/notation-go/verifier" + "github.com/notaryproject/notation-go/verifier/trustpolicy" + "github.com/notaryproject/notation-go/verifier/truststore" + + clirev "github.com/notaryproject/notation/internal/revocation" +) + +// Verifier is embedded with notation.BlobVerifier and notation.Verifier. +type Verifier interface { + notation.BlobVerifier + notation.Verifier +} + +// GetVerifier returns a Verifier. +// isBlob is set to true when verifying an arbitrary blob. +func GetVerifier(ctx context.Context, isBlob bool) (Verifier, error) { + // revocation check + revocationCodeSigningValidator, err := clirev.NewRevocationValidator(ctx, purpose.CodeSigning) + if err != nil { + return nil, err + } + revocationTimestampingValidator, err := clirev.NewRevocationValidator(ctx, purpose.Timestamping) + if err != nil { + return nil, err + } + + // trust policy and trust store + x509TrustStore := truststore.NewX509TrustStore(dir.ConfigFS()) + if isBlob { + blobPolicyDocument, err := trustpolicy.LoadBlobDocument() + if err != nil { + return nil, err + } + return verifier.NewVerifierWithOptions(nil, blobPolicyDocument, x509TrustStore, plugin.NewCLIManager(dir.PluginFS()), verifier.VerifierOptions{ + RevocationCodeSigningValidator: revocationCodeSigningValidator, + RevocationTimestampingValidator: revocationTimestampingValidator, + }) + } + + policyDocument, err := trustpolicy.LoadOCIDocument() + if err != nil { + return nil, err + } + return verifier.NewVerifierWithOptions(policyDocument, nil, x509TrustStore, plugin.NewCLIManager(dir.PluginFS()), verifier.VerifierOptions{ + RevocationCodeSigningValidator: revocationCodeSigningValidator, + RevocationTimestampingValidator: revocationTimestampingValidator, + }) +} diff --git a/internal/cmd/verifier_test.go b/internal/cmd/verifier_test.go new file mode 100644 index 000000000..8a7e5bc82 --- /dev/null +++ b/internal/cmd/verifier_test.go @@ -0,0 +1,162 @@ +// Copyright The Notary Project Authors. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package cmd + +import ( + "context" + "encoding/json" + "os" + "path/filepath" + "testing" + + "github.com/notaryproject/notation-go/dir" + "github.com/notaryproject/notation-go/verifier/trustpolicy" +) + +func TestGetVerifier(t *testing.T) { + defer func(oldConfiDir, oldCacheDir string) { + dir.UserConfigDir = oldConfiDir + dir.UserCacheDir = oldCacheDir + }(dir.UserConfigDir, dir.UserCacheDir) + + t.Run("oci success", func(t *testing.T) { + tempRoot := t.TempDir() + dir.UserConfigDir = tempRoot + path := filepath.Join(tempRoot, "trustpolicy.oci.json") + policyJson, _ := json.Marshal(dummyOCIPolicyDocument(false)) + if err := os.WriteFile(path, policyJson, 0600); err != nil { + t.Fatalf("write oci policy file failed. Error: %v", err) + } + t.Cleanup(func() { os.RemoveAll(tempRoot) }) + + _, err := GetVerifier(context.Background(), false) + if err != nil { + t.Fatal(err) + } + }) + + t.Run("blob success", func(t *testing.T) { + tempRoot := t.TempDir() + dir.UserConfigDir = tempRoot + path := filepath.Join(tempRoot, "trustpolicy.blob.json") + policyJson, _ := json.Marshal(dummyBlobPolicyDocument(false)) + if err := os.WriteFile(path, policyJson, 0600); err != nil { + t.Fatalf("write blob policy file failed. Error: %v", err) + } + t.Cleanup(func() { os.RemoveAll(tempRoot) }) + + _, err := GetVerifier(context.Background(), true) + if err != nil { + t.Fatal(err) + } + }) + + t.Run("non-existing oci trust policy", func(t *testing.T) { + dir.UserConfigDir = "/" + expectedErrMsg := "trust policy is not present. To create a trust policy, see: https://notaryproject.dev/docs/quickstart/#create-a-trust-policy" + _, err := GetVerifier(context.Background(), false) + if err == nil || err.Error() != expectedErrMsg { + t.Fatalf("expected %s, but got %s", expectedErrMsg, err) + } + }) + + t.Run("non-existing blob trust policy", func(t *testing.T) { + dir.UserConfigDir = "/" + expectedErrMsg := "trust policy is not present. To create a trust policy, see: https://notaryproject.dev/docs/quickstart/#create-a-trust-policy" + _, err := GetVerifier(context.Background(), true) + if err == nil || err.Error() != expectedErrMsg { + t.Fatalf("expected %s, but got %s", expectedErrMsg, err) + } + }) + + t.Run("invalid oci trust policy", func(t *testing.T) { + tempRoot := t.TempDir() + dir.UserConfigDir = tempRoot + path := filepath.Join(tempRoot, "trustpolicy.oci.json") + policyJson, _ := json.Marshal(dummyOCIPolicyDocument(true)) + if err := os.WriteFile(path, policyJson, 0600); err != nil { + t.Fatalf("write oci policy file failed. Error: %v", err) + } + t.Cleanup(func() { os.RemoveAll(tempRoot) }) + + expectedErrMsg := "oci trust policy document has empty version, version must be specified" + _, err := GetVerifier(context.Background(), false) + if err == nil || err.Error() != expectedErrMsg { + t.Fatalf("expected %s, but got %s", expectedErrMsg, err) + } + }) + + t.Run("invalid blob trust policy", func(t *testing.T) { + tempRoot := t.TempDir() + dir.UserConfigDir = tempRoot + path := filepath.Join(tempRoot, "trustpolicy.blob.json") + policyJson, _ := json.Marshal(dummyBlobPolicyDocument(true)) + if err := os.WriteFile(path, policyJson, 0600); err != nil { + t.Fatalf("write blob policy file failed. Error: %v", err) + } + t.Cleanup(func() { os.RemoveAll(tempRoot) }) + + expectedErrMsg := "blob trust policy has empty version, version must be specified" + _, err := GetVerifier(context.Background(), true) + if err == nil || err.Error() != expectedErrMsg { + t.Fatalf("expected %s, but got %s", expectedErrMsg, err) + } + }) +} + +func dummyOCIPolicyDocument(invalid bool) trustpolicy.OCIDocument { + if invalid { + return trustpolicy.OCIDocument{ + Version: "", + } + } + return trustpolicy.OCIDocument{ + Version: "1.0", + TrustPolicies: []trustpolicy.OCITrustPolicy{ + { + Name: "test-oci-statement", + RegistryScopes: []string{"registry.acme-rockets.io/software/net-monitor"}, + SignatureVerification: trustpolicy.SignatureVerification{VerificationLevel: "strict"}, + TrustStores: []string{"ca:valid-trust-store", "signingAuthority:valid-trust-store"}, + TrustedIdentities: []string{"x509.subject:CN=Notation Test Root,O=Notation,L=Seattle,ST=WA,C=US"}, + }, + }, + } +} + +func dummyBlobPolicyDocument(invalid bool) trustpolicy.BlobDocument { + if invalid { + return trustpolicy.BlobDocument{ + Version: "", + } + } + return trustpolicy.BlobDocument{ + Version: "1.0", + TrustPolicies: []trustpolicy.BlobTrustPolicy{ + { + Name: "test-blob-statement", + SignatureVerification: trustpolicy.SignatureVerification{VerificationLevel: "strict"}, + TrustStores: []string{"ca:valid-trust-store", "signingAuthority:valid-trust-store"}, + TrustedIdentities: []string{"x509.subject:CN=Notation Test Root,O=Notation,L=Seattle,ST=WA,C=US"}, + }, + { + Name: "test-blob-statement-global", + SignatureVerification: trustpolicy.SignatureVerification{VerificationLevel: "strict"}, + TrustStores: []string{"ca:valid-trust-store", "signingAuthority:valid-trust-store"}, + TrustedIdentities: []string{"x509.subject:CN=Notation Test Root,O=Notation,L=Seattle,ST=WA,C=US"}, + GlobalPolicy: true, + }, + }, + } +} diff --git a/internal/ioutil/print.go b/internal/ioutil/print.go index a7a3bac6c..87718dfec 100644 --- a/internal/ioutil/print.go +++ b/internal/ioutil/print.go @@ -15,12 +15,19 @@ package ioutil import ( "encoding/json" + "errors" "fmt" "io" + "io/fs" + "os" "path/filepath" + "reflect" "text/tabwriter" + "github.com/notaryproject/notation-go" "github.com/notaryproject/notation-go/config" + "github.com/notaryproject/notation-go/verifier/trustpolicy" + "github.com/notaryproject/notation-go/verifier/truststore" ) func newTabWriter(w io.Writer) *tabwriter.Writer { @@ -91,3 +98,72 @@ func PrintObjectAsJSON(i interface{}) error { return nil } + +// PrintVerificationFailure prints out messages when verification fails +func PrintVerificationFailure(outcomes []*notation.VerificationOutcome, printOut string, err error, isBlob bool) error { + // write out on failure + if err != nil || len(outcomes) == 0 { + if err != nil { + var errTrustStore truststore.TrustStoreError + if errors.As(err, &errTrustStore) { + if errors.Is(err, fs.ErrNotExist) { + return fmt.Errorf("%w. Use command 'notation cert add' to create and add trusted certificates to the trust store", errTrustStore) + } else { + return fmt.Errorf("%w. %w", errTrustStore, errTrustStore.InnerError) + } + } + + var errCertificate truststore.CertificateError + if errors.As(err, &errCertificate) { + if errors.Is(err, fs.ErrNotExist) { + return fmt.Errorf("%w. Use command 'notation cert add' to create and add trusted certificates to the trust store", errCertificate) + } else { + return fmt.Errorf("%w. %w", errCertificate, errCertificate.InnerError) + } + } + + var errorVerificationFailed notation.ErrorVerificationFailed + if !errors.As(err, &errorVerificationFailed) { + return fmt.Errorf("signature verification failed: %w", err) + } + } + if isBlob { + return fmt.Errorf("provided signature verification failed against blob %s", printOut) + } + return fmt.Errorf("signature verification failed for all the signatures associated with %s", printOut) + } + return nil +} + +// PrintVerificationSuccess prints out messages when verification succeeds +func PrintVerificationSuccess(outcomes []*notation.VerificationOutcome, printout string) { + // write out on success + outcome := outcomes[0] + // print out warning for any failed result with logged verification action + for _, result := range outcome.VerificationResults { + if result.Error != nil { + // at this point, the verification action has to be logged and + // it's failed + fmt.Fprintf(os.Stderr, "Warning: %v was set to %q and failed with error: %v\n", result.Type, result.Action, result.Error) + } + } + if reflect.DeepEqual(outcome.VerificationLevel, trustpolicy.LevelSkip) { + fmt.Println("Trust policy is configured to skip signature verification for", printout) + } else { + fmt.Println("Successfully verified signature for", printout) + PrintMetadataIfPresent(outcome) + } +} + +// PrintMetadataIfPresent prints out user metadata if present +func PrintMetadataIfPresent(outcome *notation.VerificationOutcome) { + // the signature envelope is parsed as part of verification. + // since user metadata is only printed on successful verification, + // this error can be ignored + metadata, _ := outcome.UserMetadata() + + if len(metadata) > 0 { + fmt.Println("\nThe artifact was signed with the following user metadata.") + PrintMetadataMap(os.Stdout, metadata) + } +} diff --git a/internal/ioutil/print_test.go b/internal/ioutil/print_test.go new file mode 100644 index 000000000..02c976ce5 --- /dev/null +++ b/internal/ioutil/print_test.go @@ -0,0 +1,29 @@ +// Copyright The Notary Project Authors. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package ioutil + +import ( + "testing" + + "github.com/notaryproject/notation-go" +) + +func TestBlobVerificateFailure(t *testing.T) { + var outcomes []*notation.VerificationOutcome + expectedErrMsg := "provided signature verification failed against blob myblob" + err := PrintVerificationFailure(outcomes, "myblob", nil, true) + if err == nil || err.Error() != expectedErrMsg { + t.Fatalf("expected %s, but got %s", expectedErrMsg, err) + } +} diff --git a/specs/commandline/blob.md b/specs/commandline/blob.md index 532d633f7..6d683b287 100644 --- a/specs/commandline/blob.md +++ b/specs/commandline/blob.md @@ -6,11 +6,11 @@ Use `notation blob` command to sign, verify, and inspect signatures associated w Users can use `notation blob policy` command to manage trust policies for verifying a blob signature. The `notation blob policy` command provides a user-friendly way to manage trust policies for signed blobs. It allows users to show trust policy configuration, import/export a trust policy configuration file from/to a JSON file. For more details, see [blob trust policy specification and examples](https://github.com/notaryproject/specifications/blob/main/specs/trust-store-trust-policy.md#blob-trust-policy). -The sample trust policy file (`trustpolicy.blob.json`) for verifying signed blobs is shown below. This sample trust policy file, contains three different statements for different usecases: +The sample trust policy file (`trustpolicy.blob.json`) for verifying signed blobs is shown below. This sample trust policy file, contains three different statements for different use cases: -- The Policy named "wabbit-networks-policy" is for verifying blob artifacts signed by Wabbit Networks. -- Policy named "skip-verification-policy" is for skipping verification on blob artifacts. -- Policy "global-verification-policy" is for auditing verification results when user does not provide `--policy-name` argument in `notation blob verify` command. +- The policy named "wabbit-networks-policy" is for verifying blob artifacts signed by Wabbit Networks. +- The policy named "skip-verification-policy" is for skipping verification on blob artifacts. +- The policy "global-verification-policy" is for auditing verification results when user does not set the `--policy-name` flag in `notation blob verify` command. ```jsonc { @@ -160,15 +160,14 @@ Usage: notation blob verify [flags] --signature Flags: - --signature string location of the blob signature file - --media-type string optional media type of the blob to verify - --policy-name string optional policy name to verify against. If not provided, notation verifies against the global policy if it exists. - -m, --user-metadata stringArray user defined {key}={value} pairs that must be present in the signature for successful verification if provided + -d, --debug debug mode + -h, --help help for verify + --media-type string media type of the blob to verify --plugin-config stringArray {key}={value} pairs that are passed as it is to a plugin, if the verification is associated with a verification plugin, refer plugin documentation to set appropriate values - -o, --output string output format, options: 'json', 'text' (default "text") - -d, --debug debug mode - -v, --verbose verbose mode - -h, --help help for inspect + --policy-name string policy name to verify against. If not provided, the global policy is used if exists + --signature string filepath of the signature to be verified + -m, --user-metadata stringArray user defined {key}={value} pairs that must be present in the signature for successful verification if provided + -v, --verbose verbose mode ``` ## Usage @@ -379,10 +378,10 @@ The steps to update blob trust policy configuration: ``` ## Verify blob signatures -The `notation blob verify` command can be used to verify blob signatures. In order to verify signatures, user will need to setup a trust policy file `trustpolicy.blob.json` with Policies for blobs. Below are two examples of how a policy configuration file can be setup for verifying blob signatures. +The `notation blob verify` command can be used to verify blob signatures. In order to verify signatures, user will need to setup a trust policy file `trustpolicy.blob.json` with policies for blobs. Below are two examples of how a policy configuration file can be setup for verifying blob signatures. -- The Policy named "wabbit-networks-policy" is for verifying blob artifacts signed by Wabbit Networks. -- Policy named "global-verification-policy" is for auditing verification results when user doesn't not provide `--policy-name` argument in `notation blob verify` command. +- The policy named "wabbit-networks-policy" is for verifying blob artifacts signed by Wabbit Networks. +- The policy named "global-verification-policy" is for auditing verification results when user does not set the `--policy-name` flag in `notation blob verify` command. ```jsonc { diff --git a/test/e2e/internal/notation/host.go b/test/e2e/internal/notation/host.go index d362e1fe2..1dbf0d702 100644 --- a/test/e2e/internal/notation/host.go +++ b/test/e2e/internal/notation/host.go @@ -140,14 +140,26 @@ func Opts(options ...utils.HostOption) []utils.HostOption { return options } -// BaseOptions returns a list of base Options for a valid notation. +// BaseOptions returns a list of base Options for a valid notation // testing environment. func BaseOptions() []utils.HostOption { return Opts( AuthOption("", ""), AddKeyOption(filepath.Join(NotationE2ELocalKeysDir, "e2e.key"), filepath.Join(NotationE2ELocalKeysDir, "e2e.crt")), AddTrustStoreOption("e2e", filepath.Join(NotationE2ELocalKeysDir, "e2e.crt")), - AddTrustPolicyOption("trustpolicy.json"), + AddTrustPolicyOption("trustpolicy.json", false), + ) +} + +// BaseBlobOptions returns a list of base blob options for a valid notation +// testing environment. +func BaseBlobOptions() []utils.HostOption { + return Opts( + AddKeyOption(filepath.Join(NotationE2ELocalKeysDir, "e2e.key"), filepath.Join(NotationE2ELocalKeysDir, "e2e.crt")), + AddTrustStoreOption("e2e", filepath.Join(NotationE2ELocalKeysDir, "e2e.crt")), + AddTrustPolicyOption("trustpolicy.blob.json", true), + AddTimestampTrustStoreOption("e2e", filepath.Join(NotationE2EConfigPath, "timestamp", "globalsignTSARoot.cer")), + AddTimestampTrustStoreOption("e2e", filepath.Join(NotationE2EConfigPath, "timestamp", "DigiCertTSARootSHA384.cer")), ) } @@ -156,9 +168,9 @@ func BaseOptions() []utils.HostOption { func TimestampOptions(verifyTimestamp string) []utils.HostOption { var trustPolicyOption utils.HostOption if verifyTimestamp == "afterCertExpiry" { - trustPolicyOption = AddTrustPolicyOption("timestamp_after_cert_expiry_trustpolicy.json") + trustPolicyOption = AddTrustPolicyOption("timestamp_after_cert_expiry_trustpolicy.json", false) } else { - trustPolicyOption = AddTrustPolicyOption("timestamp_trustpolicy.json") + trustPolicyOption = AddTrustPolicyOption("timestamp_trustpolicy.json", false) } return Opts( @@ -176,7 +188,7 @@ func CRLOptions() []utils.HostOption { AuthOption("", ""), AddKeyOption(filepath.Join(NotationE2EConfigPath, "crl", "leaf.key"), filepath.Join(NotationE2EConfigPath, "crl", "certchain_with_crl.pem")), AddTrustStoreOption("e2e", filepath.Join(NotationE2EConfigPath, "crl", "root.crt")), - AddTrustPolicyOption("trustpolicy.json"), + AddTrustPolicyOption("trustpolicy.json", false), ) } @@ -185,7 +197,7 @@ func BaseOptionsWithExperimental() []utils.HostOption { AuthOption("", ""), AddKeyOption(filepath.Join(NotationE2ELocalKeysDir, "e2e.key"), filepath.Join(NotationE2ELocalKeysDir, "e2e.crt")), AddTrustStoreOption("e2e", filepath.Join(NotationE2ELocalKeysDir, "e2e.crt")), - AddTrustPolicyOption("trustpolicy.json"), + AddTrustPolicyOption("trustpolicy.json", false), EnableExperimental(), ) } @@ -196,7 +208,7 @@ func TestLoginOptions() []utils.HostOption { return Opts( AddKeyOption(filepath.Join(NotationE2ELocalKeysDir, "e2e.key"), filepath.Join(NotationE2ELocalKeysDir, "e2e.crt")), AddTrustStoreOption("e2e", filepath.Join(NotationE2ELocalKeysDir, "e2e.crt")), - AddTrustPolicyOption("trustpolicy.json"), + AddTrustPolicyOption("trustpolicy.json", false), AddConfigJsonOption("pass_credential_helper_config.json"), ) } @@ -251,7 +263,15 @@ func AddTimestampTrustStoreOption(namedstore string, srcCertPath string) utils.H } // AddTrustPolicyOption adds a valid trust policy for testing. -func AddTrustPolicyOption(trustpolicyName string) utils.HostOption { +func AddTrustPolicyOption(trustpolicyName string, isBlob bool) utils.HostOption { + if isBlob { + return func(vhost *utils.VirtualHost) error { + return copyFile( + filepath.Join(BlobTrustPolicyPath, trustpolicyName), + vhost.AbsolutePath(NotationDirName, BlobTrustPolicyName), + ) + } + } return func(vhost *utils.VirtualHost) error { return copyFile( filepath.Join(NotationE2ETrustPolicyDir, trustpolicyName), diff --git a/test/e2e/internal/notation/init.go b/test/e2e/internal/notation/init.go index c082a6780..1bd92b9ac 100644 --- a/test/e2e/internal/notation/init.go +++ b/test/e2e/internal/notation/init.go @@ -23,13 +23,14 @@ import ( ) const ( - NotationDirName = "notation" - TrustPolicyName = "trustpolicy.json" - TrustStoreDirName = "truststore" - TrustStoreTypeCA = "ca" - PluginDirName = "plugins" - PluginName = "e2e-plugin" - ConfigJsonName = "config.json" + NotationDirName = "notation" + BlobTrustPolicyName = "trustpolicy.blob.json" + TrustPolicyName = "trustpolicy.json" + TrustStoreDirName = "truststore" + TrustStoreTypeCA = "ca" + PluginDirName = "plugins" + PluginName = "e2e-plugin" + ConfigJsonName = "config.json" ) const ( @@ -47,6 +48,7 @@ const ( envKeyTestRepo = "NOTATION_E2E_TEST_REPO" envKeyTestTag = "NOTATION_E2E_TEST_TAG" envKeyBlobPath = "NOTATION_E2E_BLOB_PATH" + envKeyNotationE2EBlobTrustPolicyPath = "NOTATION_E2E_BLOB_TRUST_POLICY_PATH" ) var ( @@ -71,6 +73,7 @@ var ( TestTag string RegistryStoragePath string BlobPath string + BlobTrustPolicyPath string ) func init() { @@ -87,6 +90,7 @@ func setUp() { setPathValue(envKeyOCILayoutPath, &OCILayoutPath) setPathValue(envKeyBlobPath, &BlobPath) + setPathValue(envKeyNotationE2EBlobTrustPolicyPath, &BlobTrustPolicyPath) setValue(envKeyTestRepo, &TestRepoUri) setValue(envKeyTestTag, &TestTag) } diff --git a/test/e2e/run.sh b/test/e2e/run.sh index df89d4dca..af9aae7d7 100755 --- a/test/e2e/run.sh +++ b/test/e2e/run.sh @@ -117,6 +117,7 @@ export NOTATION_E2E_PLUGIN_PATH=$CWD/plugin/bin/$PLUGIN_NAME export NOTATION_E2E_PLUGIN_TAR_GZ_PATH=$CWD/plugin/bin/$PLUGIN_NAME.tar.gz export NOTATION_E2E_MALICIOUS_PLUGIN_ARCHIVE_PATH=$CWD/testdata/malicious-plugin export NOTATION_E2E_BLOB_PATH=$CWD/testdata/blob/blobFile +export NOTATION_E2E_BLOB_TRUST_POLICY_PATH=$CWD/testdata/blob/trustpolicies # run tests ginkgo -r -p -v diff --git a/test/e2e/suite/command/blob/verify.go b/test/e2e/suite/command/blob/verify.go new file mode 100644 index 000000000..c18093716 --- /dev/null +++ b/test/e2e/suite/command/blob/verify.go @@ -0,0 +1,252 @@ +// 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" +) + +var _ = Describe("notation blob verify", func() { + // Success cases + It("with blob verify", func() { + HostWithBlob(BaseBlobOptions(), func(notation *utils.ExecOpts, blobPath string, vhost *utils.VirtualHost) { + workDir := vhost.AbsolutePath() + notation.WithWorkDir(workDir).Exec("blob", "sign", blobPath). + MatchKeyWords(SignSuccessfully). + MatchKeyWords("Signature file written to") + + signaturePath := signatureFilepath(workDir, blobPath, "jws") + notation.Exec("blob", "verify", "-d", "--signature", signaturePath, blobPath). + MatchKeyWords(VerifySuccessfully). + // debug log message outputs to stderr + MatchErrKeyWords( + "Verify signature of media type application/jose+json", + "Name:test-blob-global-statement", + ) + }) + }) + + It("with COSE signature", func() { + HostWithBlob(BaseBlobOptions(), func(notation *utils.ExecOpts, blobPath string, vhost *utils.VirtualHost) { + workDir := vhost.AbsolutePath() + notation.WithWorkDir(workDir).Exec("blob", "--signature-format", "cose", "sign", blobPath). + MatchKeyWords(SignSuccessfully). + MatchKeyWords("Signature file written to") + + signaturePath := signatureFilepath(workDir, blobPath, "cose") + notation.Exec("blob", "verify", "-d", "--signature", signaturePath, blobPath). + MatchKeyWords(VerifySuccessfully). + // debug log message outputs to stderr + MatchErrKeyWords( + "Verify signature of media type application/cose", + ) + }) + }) + + It("with policy name", func() { + HostWithBlob(BaseBlobOptions(), func(notation *utils.ExecOpts, blobPath string, vhost *utils.VirtualHost) { + workDir := vhost.AbsolutePath() + notation.WithWorkDir(workDir).Exec("blob", "sign", blobPath). + MatchKeyWords(SignSuccessfully). + MatchKeyWords("Signature file written to") + + signaturePath := signatureFilepath(workDir, blobPath, "jws") + notation.Exec("blob", "verify", "-d", "--policy-name", "test-blob-statement", "--signature", signaturePath, blobPath). + MatchKeyWords(VerifySuccessfully). + // debug log message outputs to stderr + MatchErrKeyWords( + "Name:test-blob-statement", + ) + }) + }) + + It("with media type", func() { + HostWithBlob(BaseBlobOptions(), func(notation *utils.ExecOpts, blobPath string, vhost *utils.VirtualHost) { + workDir := vhost.AbsolutePath() + notation.WithWorkDir(workDir).Exec("blob", "sign", "--media-type", "image/jpeg", blobPath). + MatchKeyWords(SignSuccessfully). + MatchKeyWords("Signature file written to") + + signaturePath := signatureFilepath(workDir, blobPath, "jws") + notation.Exec("blob", "verify", "--media-type", "image/jpeg", "--signature", signaturePath, blobPath). + MatchKeyWords(VerifySuccessfully) + }) + }) + + It("with timestamping", func() { + HostWithBlob(BaseBlobOptions(), func(notation *utils.ExecOpts, blobPath string, vhost *utils.VirtualHost) { + workDir := vhost.AbsolutePath() + notation.WithWorkDir(workDir).Exec("blob", "sign", "--timestamp-url", tsaURL, "--timestamp-root-cert", filepath.Join(NotationE2EConfigPath, "timestamp", "DigiCertTSARootSHA384.cer"), blobPath, "-d"). + MatchKeyWords(SignSuccessfully). + MatchKeyWords("Signature file written to") + + signaturePath := signatureFilepath(workDir, blobPath, "jws") + notation.Exec("blob", "verify", "-d", "--policy-name", "test-blob-with-timestamping", "--signature", signaturePath, blobPath). + MatchKeyWords(VerifySuccessfully). + // debug log message outputs to stderr + MatchErrKeyWords( + "Timestamp verification: Success", + ) + }) + }) + + It("with user metadata", func() { + HostWithBlob(BaseBlobOptions(), func(notation *utils.ExecOpts, blobPath string, vhost *utils.VirtualHost) { + workDir := vhost.AbsolutePath() + notation.WithWorkDir(workDir).Exec("blob", "sign", "--user-metadata", "k1=v1", blobPath). + MatchKeyWords(SignSuccessfully). + MatchKeyWords("Signature file written to") + + signaturePath := signatureFilepath(workDir, blobPath, "jws") + notation.Exec("blob", "verify", "--user-metadata", "k1=v1", "--signature", signaturePath, blobPath). + MatchKeyWords(VerifySuccessfully) + }) + }) + + // Failure cases + It("with missing --signature flag", func() { + HostWithBlob(BaseBlobOptions(), func(notation *utils.ExecOpts, blobPath string, vhost *utils.VirtualHost) { + workDir := vhost.AbsolutePath() + notation.WithWorkDir(workDir).Exec("blob", "sign", blobPath). + MatchKeyWords(SignSuccessfully). + MatchKeyWords("Signature file written to") + + notation.ExpectFailure().Exec("blob", "verify", blobPath). + MatchErrKeyWords("filepath of the signature cannot be empty") + }) + }) + + It("with no permission to read blob", func() { + HostWithBlob(BaseBlobOptions(), func(notation *utils.ExecOpts, blobPath string, vhost *utils.VirtualHost) { + workDir := vhost.AbsolutePath() + noPermissionBlobPath := filepath.Join(workDir, "noPermissionBlob") + newBlobFile, err := os.Create(noPermissionBlobPath) + if err != nil { + Fail(err.Error()) + } + defer newBlobFile.Close() + + notation.WithWorkDir(workDir).Exec("blob", "sign", noPermissionBlobPath). + MatchKeyWords(SignSuccessfully). + MatchKeyWords("Signature file written to") + if err := os.Chmod(noPermissionBlobPath, 0000); err != nil { + Fail(err.Error()) + } + defer os.Chmod(noPermissionBlobPath, 0700) + + signaturePath := signatureFilepath(workDir, noPermissionBlobPath, "jws") + notation.ExpectFailure().Exec("blob", "verify", "--signature", signaturePath, noPermissionBlobPath). + MatchErrKeyWords("permission denied") + }) + }) + + It("with no permission to read signature file", func() { + HostWithBlob(BaseBlobOptions(), func(notation *utils.ExecOpts, blobPath string, vhost *utils.VirtualHost) { + workDir := vhost.AbsolutePath() + notation.WithWorkDir(workDir).Exec("blob", "sign", blobPath). + MatchKeyWords(SignSuccessfully). + MatchKeyWords("Signature file written to") + noPermissionSignaturePath := signatureFilepath(workDir, blobPath, "jws") + if err := os.Chmod(noPermissionSignaturePath, 0000); err != nil { + Fail(err.Error()) + } + defer os.Chmod(noPermissionSignaturePath, 0700) + + notation.ExpectFailure().Exec("blob", "verify", "--signature", noPermissionSignaturePath, blobPath). + MatchErrKeyWords("permission denied") + }) + }) + + It("with invalid plugin-config", func() { + HostWithBlob(BaseBlobOptions(), func(notation *utils.ExecOpts, blobPath string, vhost *utils.VirtualHost) { + workDir := vhost.AbsolutePath() + notation.WithWorkDir(workDir).Exec("blob", "sign", blobPath). + MatchKeyWords(SignSuccessfully). + MatchKeyWords("Signature file written to") + + signaturePath := signatureFilepath(workDir, blobPath, "jws") + notation.ExpectFailure().Exec("blob", "verify", "--plugin-config", "invalid", "--signature", signaturePath, blobPath). + MatchErrKeyWords(`could not parse flag plugin-config: key-value pair requires "=" as separator`) + }) + }) + + It("with invalid user metadata", func() { + HostWithBlob(BaseBlobOptions(), func(notation *utils.ExecOpts, blobPath string, vhost *utils.VirtualHost) { + workDir := vhost.AbsolutePath() + notation.WithWorkDir(workDir).Exec("blob", "sign", blobPath). + MatchKeyWords(SignSuccessfully). + MatchKeyWords("Signature file written to") + + signaturePath := signatureFilepath(workDir, blobPath, "jws") + notation.ExpectFailure().Exec("blob", "verify", "--user-metadata", "invalid", "--signature", signaturePath, blobPath). + MatchErrKeyWords(`could not parse flag user-metadata: key-value pair requires "=" as separator`) + }) + }) + + It("with invalid signature format", func() { + HostWithBlob(BaseBlobOptions(), func(notation *utils.ExecOpts, blobPath string, vhost *utils.VirtualHost) { + workDir := vhost.AbsolutePath() + notation.WithWorkDir(workDir).Exec("blob", "sign", blobPath). + MatchKeyWords(SignSuccessfully). + MatchKeyWords("Signature file written to") + + signaturePath := signatureFilepath(workDir, blobPath, "jws") + invalidSignaturePath := signatureFilepath(workDir, blobPath, "invalid") + if err := os.Rename(signaturePath, invalidSignaturePath); err != nil { + Fail(err.Error()) + } + notation.ExpectFailure().Exec("blob", "verify", "--signature", invalidSignaturePath, blobPath). + MatchErrKeyWords("unsupported signature format invalid") + }) + }) + + It("with mismatch media type", func() { + HostWithBlob(BaseBlobOptions(), func(notation *utils.ExecOpts, blobPath string, vhost *utils.VirtualHost) { + workDir := vhost.AbsolutePath() + notation.WithWorkDir(workDir).Exec("blob", "sign", blobPath). + MatchKeyWords(SignSuccessfully). + MatchKeyWords("Signature file written to") + + signaturePath := signatureFilepath(workDir, blobPath, "jws") + notation.ExpectFailure().Exec("blob", "verify", "--media-type", "image/jpeg", "--signature", signaturePath, blobPath). + MatchErrKeyWords("integrity check failed. signature does not match the given blob") + }) + }) + + It("with no trust policy", func() { + HostWithBlob(BaseOptions(), func(notation *utils.ExecOpts, blobPath string, vhost *utils.VirtualHost) { + workDir := vhost.AbsolutePath() + notation.WithWorkDir(workDir).Exec("blob", "sign", blobPath). + MatchKeyWords(SignSuccessfully). + MatchKeyWords("Signature file written to") + + signaturePath := signatureFilepath(workDir, blobPath, "jws") + notation.ExpectFailure().Exec("blob", "verify", "--signature", signaturePath, blobPath). + MatchErrKeyWords(`trust policy is not present. To create a trust policy, see: https://notaryproject.dev/docs/quickstart/#create-a-trust-policy`) + }) + }) +}) + +func signatureFilepath(signatureDirectory, blobPath, signatureFormat string) string { + blobFilename := filepath.Base(blobPath) + signatureFilename := fmt.Sprintf("%s.%s.sig", blobFilename, signatureFormat) + return filepath.Join(signatureDirectory, signatureFilename) +} diff --git a/test/e2e/suite/command/policy.go b/test/e2e/suite/command/policy.go index 118939c58..c597228e9 100644 --- a/test/e2e/suite/command/policy.go +++ b/test/e2e/suite/command/policy.go @@ -35,7 +35,7 @@ var _ = Describe("trust policy maintainer", func() { }) It("should show error and hint if policy without read permission", func() { - Host(Opts(AddTrustPolicyOption(TrustPolicyName)), func(notation *utils.ExecOpts, artifact *Artifact, vhost *utils.VirtualHost) { + Host(Opts(AddTrustPolicyOption(TrustPolicyName, false)), func(notation *utils.ExecOpts, artifact *Artifact, vhost *utils.VirtualHost) { trustPolicyPath := vhost.AbsolutePath(NotationDirName, TrustPolicyName) os.Chmod(trustPolicyPath, 0200) notation.ExpectFailure(). @@ -47,7 +47,7 @@ var _ = Describe("trust policy maintainer", func() { It("should show exist policy", func() { content, err := os.ReadFile(filepath.Join(NotationE2ETrustPolicyDir, TrustPolicyName)) Expect(err).NotTo(HaveOccurred()) - Host(Opts(AddTrustPolicyOption(TrustPolicyName)), func(notation *utils.ExecOpts, artifact *Artifact, vhost *utils.VirtualHost) { + Host(Opts(AddTrustPolicyOption(TrustPolicyName, false)), func(notation *utils.ExecOpts, artifact *Artifact, vhost *utils.VirtualHost) { notation.Exec("policy", "show"). MatchContent(string(content)) }) @@ -57,7 +57,7 @@ var _ = Describe("trust policy maintainer", func() { policyName := "invalid_format_trustpolicy.json" content, err := os.ReadFile(filepath.Join(NotationE2ETrustPolicyDir, policyName)) Expect(err).NotTo(HaveOccurred()) - Host(Opts(AddTrustPolicyOption(policyName)), func(notation *utils.ExecOpts, artifact *Artifact, vhost *utils.VirtualHost) { + Host(Opts(AddTrustPolicyOption(policyName, false)), func(notation *utils.ExecOpts, artifact *Artifact, vhost *utils.VirtualHost) { notation.Exec("policy", "show"). MatchErrKeyWords("existing trust policy configuration is invalid"). MatchContent(string(content)) @@ -126,7 +126,7 @@ var _ = Describe("trust policy maintainer", func() { }) When("importing configuration with existing trust policy configuration", func() { - opts := Opts(AddTrustPolicyOption(TrustPolicyName)) + opts := Opts(AddTrustPolicyOption(TrustPolicyName, false)) It("should fail if no file path is provided", func() { Host(opts, func(notation *utils.ExecOpts, artifact *Artifact, vhost *utils.VirtualHost) { notation.ExpectFailure(). @@ -183,7 +183,7 @@ var _ = Describe("trust policy maintainer", func() { }) It("should skip confirmation if existing policy is malformed", func() { - Host(Opts(AddTrustPolicyOption("invalid_format_trustpolicy.json")), func(notation *utils.ExecOpts, artifact *Artifact, vhost *utils.VirtualHost) { + Host(Opts(AddTrustPolicyOption("invalid_format_trustpolicy.json", false)), func(notation *utils.ExecOpts, artifact *Artifact, vhost *utils.VirtualHost) { policyFileName := "skip_trustpolicy.json" notation.Exec("policy", "import", filepath.Join(NotationE2ETrustPolicyDir, policyFileName)).MatchKeyWords(). MatchKeyWords("Trust policy configuration imported successfully.") diff --git a/test/e2e/suite/command/sign.go b/test/e2e/suite/command/sign.go index d9cefd445..590c5a6ad 100644 --- a/test/e2e/suite/command/sign.go +++ b/test/e2e/suite/command/sign.go @@ -169,7 +169,7 @@ var _ = Describe("notation sign", func() { // copy the generated cert file and create the new trust policy for verify signature with generated new key. OldNotation(AuthOption("", ""), AddTrustStoreOption(keyName, vhost.AbsolutePath(NotationDirName, LocalKeysDirName, keyName+".crt")), - AddTrustPolicyOption("generate_test_trustpolicy.json"), + AddTrustPolicyOption("generate_test_trustpolicy.json", false), ).Exec("verify", artifact.ReferenceWithTag()). MatchKeyWords(VerifySuccessfully) }) diff --git a/test/e2e/suite/scenario/quickstart.go b/test/e2e/suite/scenario/quickstart.go index b04f5b22e..96161ecb4 100644 --- a/test/e2e/suite/scenario/quickstart.go +++ b/test/e2e/suite/scenario/quickstart.go @@ -85,7 +85,7 @@ var _ = Describe("notation quickstart E2E test", Ordered, func() { }) It("Create a trust policy", func() { - vhost.SetOption(AddTrustPolicyOption("quickstart_trustpolicy.json")) + vhost.SetOption(AddTrustPolicyOption("quickstart_trustpolicy.json", false)) validator.CheckFileExist(vhost.AbsolutePath(NotationDirName, TrustPolicyName)) }) diff --git a/test/e2e/suite/trustpolicy/multi_statements.go b/test/e2e/suite/trustpolicy/multi_statements.go index 6364123b6..b9ae2bcc6 100644 --- a/test/e2e/suite/trustpolicy/multi_statements.go +++ b/test/e2e/suite/trustpolicy/multi_statements.go @@ -25,7 +25,7 @@ var _ = Describe("notation trust policy multi-statements test", func() { Host(BaseOptions(), func(notation *utils.ExecOpts, _ *Artifact, vhost *utils.VirtualHost) { artifact := GenerateArtifact("", "test-repo8") // update trustpolicy.json - vhost.SetOption(AddTrustPolicyOption("multi_statements_with_the_same_registry_scope_trustpolicy.json")) + vhost.SetOption(AddTrustPolicyOption("multi_statements_with_the_same_registry_scope_trustpolicy.json", false)) // test localhost:5000/test-repo notation.Exec("sign", artifact.ReferenceWithDigest()).MatchKeyWords(SignSuccessfully) @@ -38,7 +38,7 @@ var _ = Describe("notation trust policy multi-statements test", func() { Host(BaseOptions(), func(notation *utils.ExecOpts, _ *Artifact, vhost *utils.VirtualHost) { artifact := GenerateArtifact("e2e-valid-signature", "test-repo9") // update trustpolicy.json - vhost.SetOption(AddTrustPolicyOption("multi_statements_with_wildcard_registry_scope_trustpolicy.json")) + vhost.SetOption(AddTrustPolicyOption("multi_statements_with_wildcard_registry_scope_trustpolicy.json", false)) // test localhost:5000/test-repo notation.Exec("sign", artifact.ReferenceWithDigest()).MatchKeyWords(SignSuccessfully) @@ -51,7 +51,7 @@ var _ = Describe("notation trust policy multi-statements test", func() { Host(BaseOptions(), func(notation *utils.ExecOpts, _ *Artifact, vhost *utils.VirtualHost) { artifact := GenerateArtifact("", "test-repo10") // update trustpolicy.json - vhost.SetOption(AddTrustPolicyOption("multi_statements_with_the_same_name_trustpolicy.json")) + vhost.SetOption(AddTrustPolicyOption("multi_statements_with_the_same_name_trustpolicy.json", false)) // test localhost:5000/test-repo notation.Exec("sign", artifact.ReferenceWithDigest()).MatchKeyWords(SignSuccessfully) @@ -63,7 +63,7 @@ var _ = Describe("notation trust policy multi-statements test", func() { It("multiple statements with multi-wildcard registry scopes", func() { Host(BaseOptions(), func(notation *utils.ExecOpts, artifact *Artifact, vhost *utils.VirtualHost) { // update trustpolicy.json - vhost.SetOption(AddTrustPolicyOption("multi_statements_with_multi_wildcard_registry_scope_trustpolicy.json")) + vhost.SetOption(AddTrustPolicyOption("multi_statements_with_multi_wildcard_registry_scope_trustpolicy.json", false)) // test localhost:5000/test-repo notation.Exec("sign", artifact.ReferenceWithDigest()).MatchKeyWords(SignSuccessfully) diff --git a/test/e2e/suite/trustpolicy/registry_scope.go b/test/e2e/suite/trustpolicy/registry_scope.go index 7eb19a5f4..0e4cb0725 100644 --- a/test/e2e/suite/trustpolicy/registry_scope.go +++ b/test/e2e/suite/trustpolicy/registry_scope.go @@ -29,7 +29,7 @@ var _ = Describe("notation trust policy registryScope test", func() { It("empty registryScope", func() { Host(BaseOptions(), func(notation *utils.ExecOpts, artifact *Artifact, vhost *utils.VirtualHost) { // update trustpolicy.json - vhost.SetOption(AddTrustPolicyOption("empty_registry_scope_trustpolicy.json")) + vhost.SetOption(AddTrustPolicyOption("empty_registry_scope_trustpolicy.json", false)) // test localhost:5000/test-repo OldNotation().Exec("sign", artifact.ReferenceWithDigest()).MatchKeyWords(SignSuccessfully) @@ -41,7 +41,7 @@ var _ = Describe("notation trust policy registryScope test", func() { It("malformed registryScope", func() { Host(BaseOptions(), func(notation *utils.ExecOpts, artifact *Artifact, vhost *utils.VirtualHost) { // update trustpolicy.json - vhost.SetOption(AddTrustPolicyOption("malformed_registry_scope_trustpolicy.json")) + vhost.SetOption(AddTrustPolicyOption("malformed_registry_scope_trustpolicy.json", false)) OldNotation().Exec("sign", artifact.ReferenceWithDigest()).MatchKeyWords(SignSuccessfully) notation.ExpectFailure().Exec("verify", artifact.ReferenceWithDigest()). @@ -52,7 +52,7 @@ var _ = Describe("notation trust policy registryScope test", func() { It("registryScope with a repository", func() { Host(BaseOptions(), func(notation *utils.ExecOpts, _ *Artifact, vhost *utils.VirtualHost) { // update trustpolicy.json - vhost.SetOption(AddTrustPolicyOption("registry_scope_trustpolicy.json")) + vhost.SetOption(AddTrustPolicyOption("registry_scope_trustpolicy.json", false)) // generate an artifact with given repository name artifact := GenerateArtifact("", "test-repo") @@ -66,7 +66,7 @@ var _ = Describe("notation trust policy registryScope test", func() { It("registryScope with multiple repositories", func() { Host(BaseOptions(), func(notation *utils.ExecOpts, _ *Artifact, vhost *utils.VirtualHost) { // update trustpolicy.json - vhost.SetOption(AddTrustPolicyOption("multiple_registry_scope_trustpolicy.json")) + vhost.SetOption(AddTrustPolicyOption("multiple_registry_scope_trustpolicy.json", false)) // generate an artifact with given repository name artifact2 := GenerateArtifact("", "test-repo2") @@ -85,7 +85,7 @@ var _ = Describe("notation trust policy registryScope test", func() { It("registryScope with any(*) repository", func() { Host(BaseOptions(), func(notation *utils.ExecOpts, _ *Artifact, vhost *utils.VirtualHost) { // update trustpolicy.json - vhost.SetOption(AddTrustPolicyOption("any_registry_scope_trust_policy.json")) + vhost.SetOption(AddTrustPolicyOption("any_registry_scope_trust_policy.json", false)) // generate an artifact with given repository name artifact4 := GenerateArtifact("", "test-repo4") @@ -104,7 +104,7 @@ var _ = Describe("notation trust policy registryScope test", func() { It("overlapped registryScope", func() { Host(BaseOptions(), func(notation *utils.ExecOpts, _ *Artifact, vhost *utils.VirtualHost) { // update trustpolicy.json - vhost.SetOption(AddTrustPolicyOption("overlapped_registry_scope_trustpolicy.json")) + vhost.SetOption(AddTrustPolicyOption("overlapped_registry_scope_trustpolicy.json", false)) artifact := GenerateArtifact("", "test-repo6") @@ -118,7 +118,7 @@ var _ = Describe("notation trust policy registryScope test", func() { It("wildcard plus specific repo registryScope", func() { Host(BaseOptions(), func(notation *utils.ExecOpts, _ *Artifact, vhost *utils.VirtualHost) { // update trustpolicy.json - vhost.SetOption(AddTrustPolicyOption("wildcard_plus_other_registry_scope_trustpolicy.json")) + vhost.SetOption(AddTrustPolicyOption("wildcard_plus_other_registry_scope_trustpolicy.json", false)) artifact := GenerateArtifact("", "test-repo7") @@ -132,7 +132,7 @@ var _ = Describe("notation trust policy registryScope test", func() { It("invalid registryScope", func() { Host(BaseOptions(), func(notation *utils.ExecOpts, artifact *Artifact, vhost *utils.VirtualHost) { // update trustpolicy.json - vhost.SetOption(AddTrustPolicyOption("invalid_registry_scope_trustpolicy.json")) + vhost.SetOption(AddTrustPolicyOption("invalid_registry_scope_trustpolicy.json", false)) // test localhost:5000/test-repo OldNotation().Exec("sign", artifact.ReferenceWithDigest()).MatchKeyWords(SignSuccessfully) diff --git a/test/e2e/suite/trustpolicy/trust_store.go b/test/e2e/suite/trustpolicy/trust_store.go index d01a2fae0..9f1a90ecd 100644 --- a/test/e2e/suite/trustpolicy/trust_store.go +++ b/test/e2e/suite/trustpolicy/trust_store.go @@ -25,7 +25,7 @@ import ( var _ = Describe("notation trust policy trust store test", func() { It("unset trust store", func() { Host(BaseOptions(), func(notation *utils.ExecOpts, _ *Artifact, vhost *utils.VirtualHost) { - vhost.SetOption(AddTrustPolicyOption("unset_trust_store_trustpolicy.json")) + vhost.SetOption(AddTrustPolicyOption("unset_trust_store_trustpolicy.json", false)) artifact := GenerateArtifact("e2e-valid-signature", "") @@ -36,7 +36,7 @@ var _ = Describe("notation trust policy trust store test", func() { It("invalid trust store", func() { Host(BaseOptions(), func(notation *utils.ExecOpts, _ *Artifact, vhost *utils.VirtualHost) { - vhost.SetOption(AddTrustPolicyOption("invalid_trust_store_trustpolicy.json")) + vhost.SetOption(AddTrustPolicyOption("invalid_trust_store_trustpolicy.json", false)) artifact := GenerateArtifact("e2e-valid-signature", "") @@ -47,7 +47,7 @@ var _ = Describe("notation trust policy trust store test", func() { It("malformed trust store", func() { Host(BaseOptions(), func(notation *utils.ExecOpts, _ *Artifact, vhost *utils.VirtualHost) { - vhost.SetOption(AddTrustPolicyOption("malformed_trust_store_trustpolicy.json")) + vhost.SetOption(AddTrustPolicyOption("malformed_trust_store_trustpolicy.json", false)) artifact := GenerateArtifact("e2e-valid-signature", "") @@ -58,7 +58,7 @@ var _ = Describe("notation trust policy trust store test", func() { It("wildcard (malformed) trust store", func() { Host(BaseOptions(), func(notation *utils.ExecOpts, _ *Artifact, vhost *utils.VirtualHost) { - vhost.SetOption(AddTrustPolicyOption("wildcard_trust_store_trustpolicy.json")) + vhost.SetOption(AddTrustPolicyOption("wildcard_trust_store_trustpolicy.json", false)) artifact := GenerateArtifact("e2e-valid-signature", "") @@ -79,7 +79,7 @@ var _ = Describe("notation trust policy trust store test", func() { // setup multiple trust store vhost.SetOption(AuthOption("", ""), - AddTrustPolicyOption("multiple_trust_store_trustpolicy.json"), + AddTrustPolicyOption("multiple_trust_store_trustpolicy.json", false), AddTrustStoreOption("e2e-new", filepath.Join(NotationE2ELocalKeysDir, "new_e2e.crt")), AddTrustStoreOption("e2e", filepath.Join(NotationE2ELocalKeysDir, "e2e.crt")), ) @@ -103,7 +103,7 @@ var _ = Describe("notation trust policy trust store test", func() { // setup overlapped trust store vhost.SetOption(AuthOption("", ""), - AddTrustPolicyOption("overlapped_trust_store_trustpolicy.json"), + AddTrustPolicyOption("overlapped_trust_store_trustpolicy.json", false), AddTrustStoreOption("e2e", filepath.Join(NotationE2ELocalKeysDir, "e2e.crt"))) notation.ExpectFailure().Exec("verify", artifact.ReferenceWithDigest(), "-v"). diff --git a/test/e2e/suite/trustpolicy/trusted_identity.go b/test/e2e/suite/trustpolicy/trusted_identity.go index e37637340..e822eb1ce 100644 --- a/test/e2e/suite/trustpolicy/trusted_identity.go +++ b/test/e2e/suite/trustpolicy/trusted_identity.go @@ -25,7 +25,7 @@ import ( var _ = Describe("notation trust policy trusted identity test", func() { It("with unset trusted identity", func() { Host(BaseOptions(), func(notation *utils.ExecOpts, _ *Artifact, vhost *utils.VirtualHost) { - vhost.SetOption(AddTrustPolicyOption("unset_trusted_identity_trustpolicy.json")) + vhost.SetOption(AddTrustPolicyOption("unset_trusted_identity_trustpolicy.json", false)) artifact := GenerateArtifact("e2e-valid-signature", "") notation.ExpectFailure().Exec("verify", artifact.ReferenceWithDigest(), "-v"). @@ -35,7 +35,7 @@ var _ = Describe("notation trust policy trusted identity test", func() { It("with valid trusted identity", func() { Host(BaseOptions(), func(notation *utils.ExecOpts, _ *Artifact, vhost *utils.VirtualHost) { - vhost.SetOption(AddTrustPolicyOption("valid_trusted_identity_trustpolicy.json")) + vhost.SetOption(AddTrustPolicyOption("valid_trusted_identity_trustpolicy.json", false)) artifact := GenerateArtifact("e2e-valid-signature", "") notation.Exec("verify", artifact.ReferenceWithDigest(), "-v"). @@ -45,7 +45,7 @@ var _ = Describe("notation trust policy trusted identity test", func() { It("with invalid trusted identity", func() { Host(BaseOptions(), func(notation *utils.ExecOpts, _ *Artifact, vhost *utils.VirtualHost) { - vhost.SetOption(AddTrustPolicyOption("invalid_trusted_identity_trustpolicy.json")) + vhost.SetOption(AddTrustPolicyOption("invalid_trusted_identity_trustpolicy.json", false)) artifact := GenerateArtifact("e2e-valid-signature", "") notation.ExpectFailure().Exec("verify", artifact.ReferenceWithDigest(), "-v"). @@ -56,7 +56,7 @@ var _ = Describe("notation trust policy trusted identity test", func() { It("with malformed trusted identity", func() { Host(BaseOptions(), func(notation *utils.ExecOpts, _ *Artifact, vhost *utils.VirtualHost) { - vhost.SetOption(AddTrustPolicyOption("malformed_trusted_identity_trustpolicy.json")) + vhost.SetOption(AddTrustPolicyOption("malformed_trusted_identity_trustpolicy.json", false)) artifact := GenerateArtifact("e2e-valid-signature", "") notation.ExpectFailure().Exec("verify", artifact.ReferenceWithDigest(), "-v"). @@ -66,7 +66,7 @@ var _ = Describe("notation trust policy trusted identity test", func() { It("with empty trusted identity", func() { Host(BaseOptions(), func(notation *utils.ExecOpts, _ *Artifact, vhost *utils.VirtualHost) { - vhost.SetOption(AddTrustPolicyOption("empty_trusted_identity_trustpolicy.json")) + vhost.SetOption(AddTrustPolicyOption("empty_trusted_identity_trustpolicy.json", false)) artifact := GenerateArtifact("e2e-valid-signature", "") notation.ExpectFailure().Exec("verify", artifact.ReferenceWithDigest(), "-v"). @@ -86,7 +86,7 @@ var _ = Describe("notation trust policy trusted identity test", func() { // setup multiple trusted identity vhost.SetOption(AuthOption("", ""), - AddTrustPolicyOption("multiple_trusted_identity_trustpolicy.json"), + AddTrustPolicyOption("multiple_trusted_identity_trustpolicy.json", false), AddTrustStoreOption("e2e", filepath.Join(NotationE2ELocalKeysDir, "new_e2e.crt")), AddTrustStoreOption("e2e", filepath.Join(NotationE2ELocalKeysDir, "e2e.crt")), ) @@ -101,7 +101,7 @@ var _ = Describe("notation trust policy trusted identity test", func() { It("with overlapped trusted identities", func() { Host(BaseOptions(), func(notation *utils.ExecOpts, _ *Artifact, vhost *utils.VirtualHost) { - vhost.SetOption(AddTrustPolicyOption("overlapped_trusted_identity_trustpolicy.json")) + vhost.SetOption(AddTrustPolicyOption("overlapped_trusted_identity_trustpolicy.json", false)) artifact := GenerateArtifact("e2e-valid-signature", "") notation.ExpectFailure().Exec("verify", artifact.ReferenceWithDigest(), "-v"). @@ -111,7 +111,7 @@ var _ = Describe("notation trust policy trusted identity test", func() { It("with wildcard plus other trusted identities", func() { Host(BaseOptions(), func(notation *utils.ExecOpts, _ *Artifact, vhost *utils.VirtualHost) { - vhost.SetOption(AddTrustPolicyOption("wildcard_plus_other_trusted_identity_trustpolicy.json")) + vhost.SetOption(AddTrustPolicyOption("wildcard_plus_other_trusted_identity_trustpolicy.json", false)) artifact := GenerateArtifact("e2e-valid-signature", "") notation.ExpectFailure().Exec("verify", artifact.ReferenceWithDigest(), "-v"). @@ -121,7 +121,7 @@ var _ = Describe("notation trust policy trusted identity test", func() { It("with trusted identities missing organization", func() { Host(BaseOptions(), func(notation *utils.ExecOpts, _ *Artifact, vhost *utils.VirtualHost) { - vhost.SetOption(AddTrustPolicyOption("missing_organization_trusted_identity_trustpolicy.json")) + vhost.SetOption(AddTrustPolicyOption("missing_organization_trusted_identity_trustpolicy.json", false)) artifact := GenerateArtifact("e2e-valid-signature", "") notation.ExpectFailure().Exec("verify", artifact.ReferenceWithDigest(), "-v"). @@ -131,7 +131,7 @@ var _ = Describe("notation trust policy trusted identity test", func() { It("with trusted identities missing state", func() { Host(BaseOptions(), func(notation *utils.ExecOpts, _ *Artifact, vhost *utils.VirtualHost) { - vhost.SetOption(AddTrustPolicyOption("missing_state_trusted_identity_trustpolicy.json")) + vhost.SetOption(AddTrustPolicyOption("missing_state_trusted_identity_trustpolicy.json", false)) artifact := GenerateArtifact("e2e-valid-signature", "") notation.ExpectFailure().Exec("verify", artifact.ReferenceWithDigest(), "-v"). @@ -141,7 +141,7 @@ var _ = Describe("notation trust policy trusted identity test", func() { It("with trusted identities missing country", func() { Host(BaseOptions(), func(notation *utils.ExecOpts, _ *Artifact, vhost *utils.VirtualHost) { - vhost.SetOption(AddTrustPolicyOption("missing_country_trusted_identity_trustpolicy.json")) + vhost.SetOption(AddTrustPolicyOption("missing_country_trusted_identity_trustpolicy.json", false)) artifact := GenerateArtifact("e2e-valid-signature", "") notation.ExpectFailure().Exec("verify", artifact.ReferenceWithDigest(), "-v"). diff --git a/test/e2e/suite/trustpolicy/verification_level.go b/test/e2e/suite/trustpolicy/verification_level.go index 3f6c1197c..0ebaf3ce7 100644 --- a/test/e2e/suite/trustpolicy/verification_level.go +++ b/test/e2e/suite/trustpolicy/verification_level.go @@ -38,7 +38,7 @@ var _ = Describe("notation trust policy verification level test", func() { artifact := GenerateArtifact("e2e-with-expired-cert", "") vhost.SetOption(AuthOption("", ""), - AddTrustPolicyOption("trustpolicy.json"), + AddTrustPolicyOption("trustpolicy.json", false), AddTrustStoreOption("e2e", filepath.Join(NotationE2EConfigPath, "localkeys", "expired_e2e.crt")), ) @@ -51,7 +51,7 @@ var _ = Describe("notation trust policy verification level test", func() { It("strict level with invalid authenticity", func() { Host(nil, func(notation *utils.ExecOpts, _ *Artifact, vhost *utils.VirtualHost) { vhost.SetOption(AuthOption("", ""), - AddTrustPolicyOption("trustpolicy.json"), + AddTrustPolicyOption("trustpolicy.json", false), AddTrustStoreOption("e2e", filepath.Join(NotationE2ELocalKeysDir, "new_e2e.crt")), ) @@ -77,7 +77,7 @@ var _ = Describe("notation trust policy verification level test", func() { It("permissive level with expired signature", func() { Host(BaseOptions(), func(notation *utils.ExecOpts, _ *Artifact, vhost *utils.VirtualHost) { - vhost.SetOption(AddTrustPolicyOption("permissive_trustpolicy.json")) + vhost.SetOption(AddTrustPolicyOption("permissive_trustpolicy.json", false)) artifact := GenerateArtifact("e2e-expired-signature", "") @@ -92,7 +92,7 @@ var _ = Describe("notation trust policy verification level test", func() { artifact := GenerateArtifact("e2e-with-expired-cert", "") vhost.SetOption(AuthOption("", ""), - AddTrustPolicyOption("permissive_trustpolicy.json"), + AddTrustPolicyOption("permissive_trustpolicy.json", false), AddTrustStoreOption("e2e", filepath.Join(NotationE2EConfigPath, "localkeys", "expired_e2e.crt")), ) @@ -106,7 +106,7 @@ var _ = Describe("notation trust policy verification level test", func() { It("permissive level with invalid authenticity", func() { Host(nil, func(notation *utils.ExecOpts, _ *Artifact, vhost *utils.VirtualHost) { vhost.SetOption(AuthOption("", ""), - AddTrustPolicyOption("permissive_trustpolicy.json"), + AddTrustPolicyOption("permissive_trustpolicy.json", false), AddTrustStoreOption("e2e", filepath.Join(NotationE2ELocalKeysDir, "new_e2e.crt")), ) @@ -122,7 +122,7 @@ var _ = Describe("notation trust policy verification level test", func() { It("permissive level with invalid integrity", func() { Host(BaseOptions(), func(notation *utils.ExecOpts, _ *Artifact, vhost *utils.VirtualHost) { - vhost.SetOption(AddTrustPolicyOption("permissive_trustpolicy.json")) + vhost.SetOption(AddTrustPolicyOption("permissive_trustpolicy.json", false)) artifact := GenerateArtifact("e2e-invalid-signature", "") @@ -134,7 +134,7 @@ var _ = Describe("notation trust policy verification level test", func() { It("audit level with expired signature", func() { Host(BaseOptions(), func(notation *utils.ExecOpts, _ *Artifact, vhost *utils.VirtualHost) { - vhost.SetOption(AddTrustPolicyOption("audit_trustpolicy.json")) + vhost.SetOption(AddTrustPolicyOption("audit_trustpolicy.json", false)) artifact := GenerateArtifact("e2e-expired-signature", "") @@ -150,7 +150,7 @@ var _ = Describe("notation trust policy verification level test", func() { artifact := GenerateArtifact("e2e-with-expired-cert", "") vhost.SetOption(AuthOption("", ""), - AddTrustPolicyOption("audit_trustpolicy.json"), + AddTrustPolicyOption("audit_trustpolicy.json", false), AddTrustStoreOption("e2e", filepath.Join(NotationE2EConfigPath, "localkeys", "expired_e2e.crt")), ) @@ -164,7 +164,7 @@ var _ = Describe("notation trust policy verification level test", func() { It("audit level with invalid authenticity", func() { Host(nil, func(notation *utils.ExecOpts, _ *Artifact, vhost *utils.VirtualHost) { vhost.SetOption(AuthOption("", ""), - AddTrustPolicyOption("audit_trustpolicy.json"), + AddTrustPolicyOption("audit_trustpolicy.json", false), AddTrustStoreOption("e2e", filepath.Join(NotationE2ELocalKeysDir, "new_e2e.crt")), ) @@ -181,7 +181,7 @@ var _ = Describe("notation trust policy verification level test", func() { It("audit level with invalid integrity", func() { Host(BaseOptions(), func(notation *utils.ExecOpts, _ *Artifact, vhost *utils.VirtualHost) { - vhost.SetOption(AddTrustPolicyOption("audit_trustpolicy.json")) + vhost.SetOption(AddTrustPolicyOption("audit_trustpolicy.json", false)) artifact := GenerateArtifact("e2e-invalid-signature", "") @@ -193,7 +193,7 @@ var _ = Describe("notation trust policy verification level test", func() { It("skip level with invalid integrity", func() { Host(BaseOptions(), func(notation *utils.ExecOpts, _ *Artifact, vhost *utils.VirtualHost) { - vhost.SetOption(AddTrustPolicyOption("skip_trustpolicy.json")) + vhost.SetOption(AddTrustPolicyOption("skip_trustpolicy.json", false)) artifact := GenerateArtifact("e2e-invalid-signature", "") @@ -204,7 +204,7 @@ var _ = Describe("notation trust policy verification level test", func() { It("strict level with Expiry overridden as log level", func() { Host(BaseOptions(), func(notation *utils.ExecOpts, _ *Artifact, vhost *utils.VirtualHost) { - vhost.SetOption(AddTrustPolicyOption("override_strict_trustpolicy.json")) + vhost.SetOption(AddTrustPolicyOption("override_strict_trustpolicy.json", false)) artifact := GenerateArtifact("e2e-expired-signature", "") @@ -220,7 +220,7 @@ var _ = Describe("notation trust policy verification level test", func() { artifact := GenerateArtifact("e2e-with-expired-cert", "") vhost.SetOption(AuthOption("", ""), - AddTrustPolicyOption("override_strict_trustpolicy.json"), + AddTrustPolicyOption("override_strict_trustpolicy.json", false), AddTrustStoreOption("e2e", filepath.Join(NotationE2EConfigPath, "localkeys", "expired_e2e.crt")), ) @@ -234,7 +234,7 @@ var _ = Describe("notation trust policy verification level test", func() { It("strict level with Authenticity overridden as log level", func() { Host(nil, func(notation *utils.ExecOpts, _ *Artifact, vhost *utils.VirtualHost) { vhost.SetOption(AuthOption("", ""), - AddTrustPolicyOption("override_strict_trustpolicy.json"), + AddTrustPolicyOption("override_strict_trustpolicy.json", false), AddTrustStoreOption("e2e", filepath.Join(NotationE2ELocalKeysDir, "new_e2e.crt")), ) @@ -251,7 +251,7 @@ var _ = Describe("notation trust policy verification level test", func() { It("permissive level with Expiry overridden as enforce level", func() { Host(BaseOptions(), func(notation *utils.ExecOpts, _ *Artifact, vhost *utils.VirtualHost) { - vhost.SetOption(AddTrustPolicyOption("override_permissive_trustpolicy.json")) + vhost.SetOption(AddTrustPolicyOption("override_permissive_trustpolicy.json", false)) artifact := GenerateArtifact("e2e-expired-signature", "") @@ -263,12 +263,12 @@ var _ = Describe("notation trust policy verification level test", func() { It("permissive level with Authentic timestamp overridden as enforce level", func() { Host(nil, func(notation *utils.ExecOpts, _ *Artifact, vhost *utils.VirtualHost) { - vhost.SetOption(AddTrustPolicyOption("override_permissive_trustpolicy.json")) + vhost.SetOption(AddTrustPolicyOption("override_permissive_trustpolicy.json", false)) artifact := GenerateArtifact("e2e-with-expired-cert", "") vhost.SetOption(AuthOption("", ""), - AddTrustPolicyOption("trustpolicy.json"), + AddTrustPolicyOption("trustpolicy.json", false), AddTrustStoreOption("e2e", filepath.Join(NotationE2EConfigPath, "localkeys", "expired_e2e.crt")), ) @@ -281,7 +281,7 @@ var _ = Describe("notation trust policy verification level test", func() { It("permissive level with Authenticity overridden as log level", func() { Host(nil, func(notation *utils.ExecOpts, _ *Artifact, vhost *utils.VirtualHost) { vhost.SetOption(AuthOption("", ""), - AddTrustPolicyOption("override_permissive_trustpolicy.json"), + AddTrustPolicyOption("override_permissive_trustpolicy.json", false), AddTrustStoreOption("e2e", filepath.Join(NotationE2ELocalKeysDir, "new_e2e.crt")), ) @@ -297,7 +297,7 @@ var _ = Describe("notation trust policy verification level test", func() { It("permissive level with Integrity overridden as log level", func() { Host(nil, func(notation *utils.ExecOpts, _ *Artifact, vhost *utils.VirtualHost) { vhost.SetOption(AuthOption("", ""), - AddTrustPolicyOption("override_integrity_for_permissive_trustpolicy.json"), + AddTrustPolicyOption("override_integrity_for_permissive_trustpolicy.json", false), AddTrustStoreOption("e2e", filepath.Join(NotationE2ELocalKeysDir, "new_e2e.crt")), ) @@ -310,7 +310,7 @@ var _ = Describe("notation trust policy verification level test", func() { It("audit level with Expiry overridden as enforce level", func() { Host(BaseOptions(), func(notation *utils.ExecOpts, _ *Artifact, vhost *utils.VirtualHost) { - vhost.SetOption(AddTrustPolicyOption("override_audit_trustpolicy.json")) + vhost.SetOption(AddTrustPolicyOption("override_audit_trustpolicy.json", false)) artifact := GenerateArtifact("e2e-expired-signature", "") @@ -322,12 +322,12 @@ var _ = Describe("notation trust policy verification level test", func() { It("audit level with Authentic timestamp overridden as enforce level", func() { Host(nil, func(notation *utils.ExecOpts, _ *Artifact, vhost *utils.VirtualHost) { - vhost.SetOption(AddTrustPolicyOption("override_audit_trustpolicy.json")) + vhost.SetOption(AddTrustPolicyOption("override_audit_trustpolicy.json", false)) artifact := GenerateArtifact("e2e-with-expired-cert", "") vhost.SetOption(AuthOption("", ""), - AddTrustPolicyOption("trustpolicy.json"), + AddTrustPolicyOption("trustpolicy.json", false), AddTrustStoreOption("e2e", filepath.Join(NotationE2EConfigPath, "localkeys", "expired_e2e.crt")), ) @@ -340,7 +340,7 @@ var _ = Describe("notation trust policy verification level test", func() { It("audit level with Authenticity overridden as enforce level", func() { Host(nil, func(notation *utils.ExecOpts, _ *Artifact, vhost *utils.VirtualHost) { vhost.SetOption(AuthOption("", ""), - AddTrustPolicyOption("override_audit_trustpolicy.json"), + AddTrustPolicyOption("override_audit_trustpolicy.json", false), AddTrustStoreOption("e2e", filepath.Join(NotationE2ELocalKeysDir, "new_e2e.crt")), ) diff --git a/test/e2e/testdata/blob/trustpolicies/trustpolicy.blob.json b/test/e2e/testdata/blob/trustpolicies/trustpolicy.blob.json new file mode 100644 index 000000000..9b90a704a --- /dev/null +++ b/test/e2e/testdata/blob/trustpolicies/trustpolicy.blob.json @@ -0,0 +1,37 @@ +{ + "version": "1.0", + "trustPolicies": [ + { + "name": "test-blob-statement", + "signatureVerification": { + "level" : "strict" + }, + "trustStores": [ "ca:e2e" ], + "trustedIdentities": [ + "*" + ] + }, + { + "name": "test-blob-global-statement", + "signatureVerification": { + "level" : "strict" + }, + "trustStores": [ "ca:e2e" ], + "trustedIdentities": [ + "*" + ], + "globalPolicy": true + }, + { + "name": "test-blob-with-timestamping", + "signatureVerification": { + "level" : "strict", + "verifyTimestamp": "always" + }, + "trustStores": [ "ca:e2e", "tsa:e2e" ], + "trustedIdentities": [ + "*" + ] + } + ] +}