Repository: notaryproject/notation (go)

Issue:
Support clean up the source key and certificate generated by Notation
### What is the areas you would like to add the new feature to?

Notation CLI

### Is your feature request related to a problem?

`notation key delete` can only remove the key from the signing key list and `notation cert delete` can only remove the self-signed certificate from the trust store. This is by design since Notation doesn't support signing with [local keys](https://github.com/notaryproject/notation/issues/539) and managing local keys. 

Per discussion in https://github.com/notaryproject/notation/pull/606#discussion_r1154664392 and another issue #604 , users want to delete the source key and certificate generated by `notation cert generate-test` in a convenient way.  

### What solution do you propose?

Providing a flag `--cleanup` to `notation cert generate-test` to allow users to delete the specified source key and certificate generated by `notation cert generate-test`. This flag is only used to delete the test key and self-signed certificate. The keys and certificates that are not generated by Notation will not be able to be deleted with this flag. 

For example, delete a source key and cert generated by `notation cert generate-test "wabbit-networks.io"`:

```
$ notation cert generate-test --cleanup wabbit-networks.io
Deleted <key_name> and <cert_name> 
```

### What alternatives have you considered?

N/A

### Any additional context?

_No response_

Pull request description:
feat: add `notation certificate cleanup-test` command
This PR adds `notation certificate cleanup-test` command with E2E tests.

Resolves #647 

Patch:
diff --git a/cmd/notation/cert/cmd.go b/cmd/notation/cert/cmd.go
index 8fe3d6f..c50500d 100644
--- a/cmd/notation/cert/cmd.go
+++ b/cmd/notation/cert/cmd.go
@@ -11,6 +11,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+// Package cert provides implementation of the `notation certificate` command
 package cert
 
 import "github.com/spf13/cobra"
@@ -29,6 +30,7 @@ func Cmd() *cobra.Command {
 		certShowCommand(nil),
 		certDeleteCommand(nil),
 		certGenerateTestCommand(nil),
+		certCleanupTestCommand(nil),
 	)
 
 	return command
diff --git a/cmd/notation/internal/truststore/truststore.go b/cmd/notation/internal/truststore/truststore.go
index 2d4e073..73452d7 100644
--- a/cmd/notation/internal/truststore/truststore.go
+++ b/cmd/notation/internal/truststore/truststore.go
@@ -190,7 +190,7 @@ func DeleteCert(storeType, namedStore, cert string, confirmed bool) error {
 		return err
 	}
 	// write out on success
-	fmt.Printf("Successfully deleted %s\n", cert)
+	fmt.Printf("Successfully deleted %s from trust store %s of type %s\n", cert, namedStore, storeType)
 	return nil
 }
 
diff --git a/go.mod b/go.mod
index 93a86d4..4a85656 100644
--- a/go.mod
+++ b/go.mod
@@ -3,10 +3,10 @@ module github.com/notaryproject/notation/v2
 go 1.24.0
 
 require (
-	github.com/notaryproject/notation-core-go v1.2.0
-	github.com/notaryproject/notation-go v1.2.0-beta.1.0.20250310060348-fdcf9cc47604
+	github.com/notaryproject/notation-core-go v1.2.1-0.20250304022306-ea37e4e6c39a
+	github.com/notaryproject/notation-go v1.2.0-beta.1.0.20250324062555-fcc1ce32f892
 	github.com/notaryproject/notation-plugin-framework-go v1.0.0
-	github.com/notaryproject/tspclient-go v1.0.0
+	github.com/notaryproject/tspclient-go v1.0.1-0.20250306063739-4f55b14d9f01
 	github.com/opencontainers/go-digest v1.0.0
 	github.com/opencontainers/image-spec v1.1.1
 	github.com/sirupsen/logrus v1.9.3
diff --git a/go.sum b/go.sum
index 94954ee..b609251 100644
--- a/go.sum
+++ b/go.sum
@@ -36,14 +36,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 h1:WElMG9X0YXJhBd0A4VOxLNalTLrTjvqtIAj7JHr5X08=
-github.com/notaryproject/notation-core-go v1.2.0/go.mod h1:+y3L1dOs2/ZwJIU5Imo7BBvZ/M3CFjXkydGGdK09EtA=
-github.com/notaryproject/notation-go v1.2.0-beta.1.0.20250310060348-fdcf9cc47604 h1:uw65pHgN+NXAqHssmlRJUkcl515AQgMIOdC6tbBHHXE=
-github.com/notaryproject/notation-go v1.2.0-beta.1.0.20250310060348-fdcf9cc47604/go.mod h1:NXYZyzIawUSyv+C0Gs8bBYJ1q8a1gy78GEss8fPNZmY=
+github.com/notaryproject/notation-core-go v1.2.1-0.20250304022306-ea37e4e6c39a h1:xagHqXDQKyG4hYCzf2yrMxNGdUf1FELEYojY7dZEgP0=
+github.com/notaryproject/notation-core-go v1.2.1-0.20250304022306-ea37e4e6c39a/go.mod h1:26/FuY/XSwyGiafPFDOeUJBz+sPsWDpK+Ei4TWtcmTc=
+github.com/notaryproject/notation-go v1.2.0-beta.1.0.20250324062555-fcc1ce32f892 h1:2bD9p585QwuFQry03o19yHZzeBJOCSuol6LR64KIfto=
+github.com/notaryproject/notation-go v1.2.0-beta.1.0.20250324062555-fcc1ce32f892/go.mod h1:NXYZyzIawUSyv+C0Gs8bBYJ1q8a1gy78GEss8fPNZmY=
 github.com/notaryproject/notation-plugin-framework-go v1.0.0 h1:6Qzr7DGXoCgXEQN+1gTZWuJAZvxh3p8Lryjn5FaLzi4=
 github.com/notaryproject/notation-plugin-framework-go v1.0.0/go.mod h1:RqWSrTOtEASCrGOEffq0n8pSg2KOgKYiWqFWczRSics=
-github.com/notaryproject/tspclient-go v1.0.0 h1:AwQ4x0gX8IHnyiZB1tggpn5NFqHpTEm1SDX8YNv4Dg4=
-github.com/notaryproject/tspclient-go v1.0.0/go.mod h1:LGyA/6Kwd2FlM0uk8Vc5il3j0CddbWSHBj/4kxQDbjs=
+github.com/notaryproject/tspclient-go v1.0.1-0.20250306063739-4f55b14d9f01 h1:Ay72jBeHKqBFk6TbJWywfwzefN3Ei7Py2OzCiWU/7nk=
+github.com/notaryproject/tspclient-go v1.0.1-0.20250306063739-4f55b14d9f01/go.mod h1:3ZJPmpmdwufY23BkS+JPNktOVb5DXJ8Ik5zxvN7h670=
 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.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040=
diff --git a/specs/cmd/certificate.md b/specs/cmd/certificate.md
index 9765f48..65eb129 100644
--- a/specs/cmd/certificate.md
+++ b/specs/cmd/certificate.md
@@ -148,7 +148,7 @@ Usage:
   notation certificate cleanup-test [flags] <common_name>
 
 Flags:
-  -h, --help       help for generate-test
+  -h, --help       help for cleanup-test
   -y, --yes        do not prompt for confirmation
 ```
 


Test patch:
diff --git a/cmd/notation/cert/cleanupTest.go b/cmd/notation/cert/cleanupTest.go
new file mode 100644
index 0000000..5aafc5f
--- /dev/null
+++ b/cmd/notation/cert/cleanupTest.go
@@ -0,0 +1,132 @@
+// 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 cert
+
+import (
+	"errors"
+	"fmt"
+	"io/fs"
+	"os"
+	"path/filepath"
+
+	"github.com/notaryproject/notation-go/config"
+	"github.com/notaryproject/notation-go/dir"
+	"github.com/notaryproject/notation/v2/cmd/notation/internal/display"
+	"github.com/notaryproject/notation/v2/cmd/notation/internal/truststore"
+	"github.com/spf13/cobra"
+)
+
+type certCleanupTestOpts struct {
+	name      string
+	confirmed bool
+}
+
+func certCleanupTestCommand(opts *certCleanupTestOpts) *cobra.Command {
+	if opts == nil {
+		opts = &certCleanupTestOpts{}
+	}
+	command := &cobra.Command{
+		Use:   "cleanup-test [flags] <common_name>",
+		Short: `Clean up a test RSA key and its corresponding certificate that were generated using the "generate-test" command.`,
+		Args: func(cmd *cobra.Command, args []string) error {
+			if len(args) == 0 {
+				return errors.New("missing certificate common name")
+			}
+			if !truststore.IsValidFileName(args[0]) {
+				return errors.New("certificate common name must follow [a-zA-Z0-9_.-]+ format")
+			}
+			opts.name = args[0]
+			return nil
+		},
+		Long: `Clean up a test RSA key and its corresponding certificate that were generated using the "generate-test" command.
+
+Example - Clean up a test key and corresponding certificate named "wabbit-networks.io":
+  notation cert cleanup-test wabbit-networks.io
+`,
+		RunE: func(cmd *cobra.Command, args []string) error {
+			return cleanupTestCert(opts)
+		},
+	}
+	command.Flags().BoolVarP(&opts.confirmed, "yes", "y", false, "do not prompt for confirmation")
+	return command
+}
+
+func cleanupTestCert(opts *certCleanupTestOpts) error {
+	name := opts.name
+	relativeKeyPath, relativeCertPath := dir.LocalKeyPath(name)
+	certPath, _ := dir.ConfigFS().SysPath(relativeCertPath) // err is always nil
+	certFileName := filepath.Base(certPath)
+	keyPath, _ := dir.ConfigFS().SysPath(relativeKeyPath) // err is always nil
+	prompt := fmt.Sprintf(`The test key %s and its corresponding certificate will be cleaned up with the following changes:
+- Delete certificate %s.crt from trust store %s of type ca
+- Remove key %s from the key list
+- Delete key file: %s
+- Delete certificate file: %s
+
+Are you sure you want to continue?`, name, name, name, name, keyPath, certPath)
+	confirmed, err := display.AskForConfirmation(os.Stdin, prompt, opts.confirmed)
+	if err != nil {
+		return err
+	}
+	if !confirmed {
+		return nil
+	}
+
+	// 1. remove from trust store
+	err = truststore.DeleteCert("ca", name, certFileName, true)
+	if err != nil {
+		if !errors.Is(err, fs.ErrNotExist) {
+			return fmt.Errorf("failed to delete certificate %s from trust store %s of type ca: %w", certFileName, name, err)
+		}
+		fmt.Printf("Certificate %s does not exist in trust store %s of type ca\n", certFileName, name)
+	}
+
+	// 2. remove key from signingkeys.json config
+	exec := func(s *config.SigningKeys) error {
+		_, err := s.Remove(name)
+		return err
+	}
+	err = config.LoadExecSaveSigningKeys(exec)
+	if err != nil {
+		var keyNotFoundError config.KeyNotFoundError
+		if !errors.As(err, &keyNotFoundError) {
+			return fmt.Errorf("failed to remove key %s from the key list: %w", name, err)
+		}
+		fmt.Printf("Key %s does not exist in the key list\n", name)
+	} else {
+		fmt.Printf("Successfully removed key %s from the key list\n", name)
+	}
+
+	// 3. delete key and certificate files from LocalKeyPath
+	err = os.Remove(keyPath)
+	if err != nil {
+		if !errors.Is(err, fs.ErrNotExist) {
+			return fmt.Errorf("failed to delete key file %s: %w", keyPath, err)
+		}
+		fmt.Printf("Key file %s does not exist\n", keyPath)
+	} else {
+		fmt.Printf("Successfully deleted key file: %s\n", keyPath)
+	}
+	err = os.Remove(certPath)
+	if err != nil {
+		if !errors.Is(err, fs.ErrNotExist) {
+			return fmt.Errorf("failed to delete certificate file %s: %w", certPath, err)
+		}
+		fmt.Printf("Certificate file %s does not exist\n", certPath)
+	} else {
+		fmt.Printf("Successfully deleted certificate file: %s\n", certPath)
+	}
+	fmt.Println("Cleanup completed successfully")
+	return nil
+}
diff --git a/cmd/notation/cert/cleanup_test.go b/cmd/notation/cert/cleanup_test.go
new file mode 100644
index 0000000..69394cc
--- /dev/null
+++ b/cmd/notation/cert/cleanup_test.go
@@ -0,0 +1,47 @@
+// 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 cert
+
+import (
+	"reflect"
+	"testing"
+)
+
+func TestCertCleanupCommand(t *testing.T) {
+	opts := &certCleanupTestOpts{}
+	cmd := certCleanupTestCommand(opts)
+	expected := &certCleanupTestOpts{
+		name: "name",
+	}
+	if err := cmd.ParseFlags([]string{
+		"name"}); err != nil {
+		t.Fatalf("Parse Flag failed: %v", err)
+	}
+	if err := cmd.Args(cmd, cmd.Flags().Args()); err != nil {
+		t.Fatalf("Parse Args failed: %v", err)
+	}
+	if !reflect.DeepEqual(*expected, *opts) {
+		t.Fatalf("Expect cert generate-test opts: %v, got: %v", expected, opts)
+	}
+}
+
+func TestCertCleanupTestCommand_MissingArgs(t *testing.T) {
+	cmd := certCleanupTestCommand(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/cert/generateTest.go b/cmd/notation/cert/generateTest.go
index 45b3205..72c78a5 100644
--- a/cmd/notation/cert/generateTest.go
+++ b/cmd/notation/cert/generateTest.go
@@ -138,7 +138,7 @@ func generateTestCert(opts *certGenerateTestOpts) error {
 	// write out
 	fmt.Printf("%s: added to the key list\n", name)
 	if opts.isDefault {
-		fmt.Printf("%s: mark as default signing key\n", name)
+		fmt.Printf("%s: marked as default signing key\n", name)
 	}
 	return nil
 }
diff --git a/cmd/notation/internal/sign/sign_test.go b/cmd/notation/internal/sign/sign_test.go
index 24a2a45..1f4bb38 100644
--- a/cmd/notation/internal/sign/sign_test.go
+++ b/cmd/notation/internal/sign/sign_test.go
@@ -177,7 +177,7 @@ func TestGetSignerFailed(t *testing.T) {
 
 	t.Run("key not found", func(t *testing.T) {
 		dir.UserConfigDir = "./testdata/valid_signingkeys"
-		expectedErrMsg := `signing key not found`
+		expectedErrMsg := `signing key test2 not found`
 		opts := &flag.SignerFlagOpts{
 			Key: "test2",
 		}
diff --git a/test/e2e/go.mod b/test/e2e/go.mod
index 584f4ac..6791a35 100644
--- a/test/e2e/go.mod
+++ b/test/e2e/go.mod
@@ -4,7 +4,7 @@ go 1.24.0
 
 require (
 	github.com/notaryproject/notation-core-go v1.2.1-0.20250304022306-ea37e4e6c39a
-	github.com/notaryproject/notation-go v1.2.0-beta.1.0.20250310060348-fdcf9cc47604
+	github.com/notaryproject/notation-go v1.2.0-beta.1.0.20250324062555-fcc1ce32f892
 	github.com/onsi/ginkgo/v2 v2.23.3
 	github.com/onsi/gomega v1.36.3
 	github.com/opencontainers/image-spec v1.1.1
@@ -17,11 +17,13 @@ require (
 	github.com/go-task/slim-sprig/v3 v3.0.0 // indirect
 	github.com/google/go-cmp v0.7.0 // indirect
 	github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad // indirect
+	github.com/notaryproject/notation-plugin-framework-go v1.0.0 // indirect
 	github.com/notaryproject/tspclient-go v1.0.1-0.20250306063739-4f55b14d9f01 // 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
 	golang.org/x/crypto v0.36.0 // indirect
+	golang.org/x/mod v0.23.0 // indirect
 	golang.org/x/net v0.37.0 // indirect
 	golang.org/x/sync v0.12.0 // indirect
 	golang.org/x/sys v0.31.0 // indirect
diff --git a/test/e2e/go.sum b/test/e2e/go.sum
index 2082429..c9c1c22 100644
--- a/test/e2e/go.sum
+++ b/test/e2e/go.sum
@@ -12,8 +12,10 @@ github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad h1:a6HEuzUHeKH6hwfN/Z
 github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144=
 github.com/notaryproject/notation-core-go v1.2.1-0.20250304022306-ea37e4e6c39a h1:xagHqXDQKyG4hYCzf2yrMxNGdUf1FELEYojY7dZEgP0=
 github.com/notaryproject/notation-core-go v1.2.1-0.20250304022306-ea37e4e6c39a/go.mod h1:26/FuY/XSwyGiafPFDOeUJBz+sPsWDpK+Ei4TWtcmTc=
-github.com/notaryproject/notation-go v1.2.0-beta.1.0.20250310060348-fdcf9cc47604 h1:uw65pHgN+NXAqHssmlRJUkcl515AQgMIOdC6tbBHHXE=
-github.com/notaryproject/notation-go v1.2.0-beta.1.0.20250310060348-fdcf9cc47604/go.mod h1:NXYZyzIawUSyv+C0Gs8bBYJ1q8a1gy78GEss8fPNZmY=
+github.com/notaryproject/notation-go v1.2.0-beta.1.0.20250324062555-fcc1ce32f892 h1:2bD9p585QwuFQry03o19yHZzeBJOCSuol6LR64KIfto=
+github.com/notaryproject/notation-go v1.2.0-beta.1.0.20250324062555-fcc1ce32f892/go.mod h1:NXYZyzIawUSyv+C0Gs8bBYJ1q8a1gy78GEss8fPNZmY=
+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.1-0.20250306063739-4f55b14d9f01 h1:Ay72jBeHKqBFk6TbJWywfwzefN3Ei7Py2OzCiWU/7nk=
 github.com/notaryproject/tspclient-go v1.0.1-0.20250306063739-4f55b14d9f01/go.mod h1:3ZJPmpmdwufY23BkS+JPNktOVb5DXJ8Ik5zxvN7h670=
 github.com/onsi/ginkgo/v2 v2.23.3 h1:edHxnszytJ4lD9D5Jjc4tiDkPBZ3siDeJJkUZJJVkp0=
@@ -34,6 +36,8 @@ github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
 github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
 golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34=
 golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc=
+golang.org/x/mod v0.23.0 h1:Zb7khfcRGKk+kqfxFaP5tZqCnDZMjC5VtUBs87Hr6QM=
+golang.org/x/mod v0.23.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
 golang.org/x/net v0.37.0 h1:1zLorHbz+LYj7MQlSf1+2tPIIgibq2eL5xkrGk6f+2c=
 golang.org/x/net v0.37.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
 golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw=
diff --git a/test/e2e/plugin/go.mod b/test/e2e/plugin/go.mod
index 9114003..ac052eb 100644
--- a/test/e2e/plugin/go.mod
+++ b/test/e2e/plugin/go.mod
@@ -5,7 +5,7 @@ go 1.24.0
 require (
 	github.com/golang-jwt/jwt v3.2.2+incompatible
 	github.com/notaryproject/notation-core-go v1.2.1-0.20250304022306-ea37e4e6c39a
-	github.com/notaryproject/notation-go v1.2.0-beta.1.0.20250310060348-fdcf9cc47604
+	github.com/notaryproject/notation-go v1.2.0-beta.1.0.20250324062555-fcc1ce32f892
 	github.com/notaryproject/notation-plugin-framework-go v1.0.0
 	github.com/spf13/cobra v1.9.1
 )
diff --git a/test/e2e/plugin/go.sum b/test/e2e/plugin/go.sum
index 5e158f9..9021073 100644
--- a/test/e2e/plugin/go.sum
+++ b/test/e2e/plugin/go.sum
@@ -40,8 +40,8 @@ github.com/jcmturner/rpc/v2 v2.0.3 h1:7FXXj8Ti1IaVFpSAziCZWNzbNuZmnvw/i6CqLNdWfZ
 github.com/jcmturner/rpc/v2 v2.0.3/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJki3ELg/Hc=
 github.com/notaryproject/notation-core-go v1.2.1-0.20250304022306-ea37e4e6c39a h1:xagHqXDQKyG4hYCzf2yrMxNGdUf1FELEYojY7dZEgP0=
 github.com/notaryproject/notation-core-go v1.2.1-0.20250304022306-ea37e4e6c39a/go.mod h1:26/FuY/XSwyGiafPFDOeUJBz+sPsWDpK+Ei4TWtcmTc=
-github.com/notaryproject/notation-go v1.2.0-beta.1.0.20250310060348-fdcf9cc47604 h1:uw65pHgN+NXAqHssmlRJUkcl515AQgMIOdC6tbBHHXE=
-github.com/notaryproject/notation-go v1.2.0-beta.1.0.20250310060348-fdcf9cc47604/go.mod h1:NXYZyzIawUSyv+C0Gs8bBYJ1q8a1gy78GEss8fPNZmY=
+github.com/notaryproject/notation-go v1.2.0-beta.1.0.20250324062555-fcc1ce32f892 h1:2bD9p585QwuFQry03o19yHZzeBJOCSuol6LR64KIfto=
+github.com/notaryproject/notation-go v1.2.0-beta.1.0.20250324062555-fcc1ce32f892/go.mod h1:NXYZyzIawUSyv+C0Gs8bBYJ1q8a1gy78GEss8fPNZmY=
 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.1-0.20250306063739-4f55b14d9f01 h1:Ay72jBeHKqBFk6TbJWywfwzefN3Ei7Py2OzCiWU/7nk=
diff --git a/test/e2e/suite/command/blob/sign.go b/test/e2e/suite/command/blob/sign.go
index 19cd74b..a1efc4f 100644
--- a/test/e2e/suite/command/blob/sign.go
+++ b/test/e2e/suite/command/blob/sign.go
@@ -120,7 +120,7 @@ var _ = Describe("notation blob sign", func() {
 	It("with invalid key", func() {
 		HostWithBlob(BaseOptions(), func(notation *utils.ExecOpts, blobPath string, vhost *utils.VirtualHost) {
 			notation.ExpectFailure().Exec("blob", "sign", "--key", "invalid", blobPath).
-				MatchErrKeyWords("signing key not found")
+				MatchErrKeyWords("signing key invalid not found")
 		})
 	})
 
diff --git a/test/e2e/suite/command/cert.go b/test/e2e/suite/command/cert.go
index 418a8a6..ee5ea8f 100644
--- a/test/e2e/suite/command/cert.go
+++ b/test/e2e/suite/command/cert.go
@@ -14,10 +14,13 @@
 package command
 
 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"
 )
 
@@ -44,7 +47,7 @@ var _ = Describe("notation cert", func() {
 		Host(BaseOptions(), func(notation *utils.ExecOpts, artifact *Artifact, vhost *utils.VirtualHost) {
 			notation.Exec("cert", "delete", "--type", "ca", "--store", "e2e", "e2e.crt", "-y").
 				MatchKeyWords(
-					"Successfully deleted e2e.crt",
+					"Successfully deleted e2e.crt from trust store e2e of type ca",
 				)
 		})
 	})
@@ -105,4 +108,232 @@ var _ = Describe("notation cert", func() {
 				)
 		})
 	})
+
+	It("cleanup test", func() {
+		Host(BaseOptions(), func(notation *utils.ExecOpts, artifact *Artifact, vhost *utils.VirtualHost) {
+			notation.Exec("cert", "generate-test", "e2e-test").
+				MatchKeyWords(
+					"generating RSA Key with 2048 bits",
+					"generated certificate expiring on",
+					"wrote key:", "e2e-test.key",
+					"wrote certificate:", "e2e-test.crt",
+					"Successfully added e2e-test.crt to named store e2e-test of type ca",
+					"e2e-test: added to the key list",
+				)
+
+			notation.Exec("cert", "cleanup-test", "e2e-test", "-y").
+				MatchKeyWords(
+					"Successfully deleted e2e-test.crt from trust store e2e-test of type ca",
+					"Successfully removed key e2e-test from the key list",
+					"Successfully deleted key file:", "e2e-test.key",
+					"Successfully deleted certificate file:", "e2e-test.crt",
+					"Cleanup completed successfully",
+				)
+		})
+	})
+
+	It("cleanup test with key set as default", func() {
+		Host(BaseOptions(), func(notation *utils.ExecOpts, artifact *Artifact, vhost *utils.VirtualHost) {
+			notation.Exec("cert", "generate-test", "e2e-test", "--default").
+				MatchKeyWords(
+					"generating RSA Key with 2048 bits",
+					"generated certificate expiring on",
+					"wrote key:", "e2e-test.key",
+					"wrote certificate:", "e2e-test.crt",
+					"Successfully added e2e-test.crt to named store e2e-test of type ca",
+					"e2e-test: added to the key list",
+					"e2e-test: marked as default signing key",
+				)
+
+			notation.Exec("cert", "cleanup-test", "e2e-test", "-y").
+				MatchKeyWords(
+					"Successfully deleted e2e-test.crt from trust store e2e-test of type ca",
+					"Successfully removed key e2e-test from the key list",
+					"Successfully deleted key file:", "e2e-test.key",
+					"Successfully deleted certificate file:", "e2e-test.crt",
+					"Cleanup completed successfully",
+				)
+		})
+	})
+
+	It("cleanup test with same name more than one time", func() {
+		Host(BaseOptions(), func(notation *utils.ExecOpts, artifact *Artifact, vhost *utils.VirtualHost) {
+			notation.Exec("cert", "generate-test", "e2e-test").
+				MatchKeyWords(
+					"generating RSA Key with 2048 bits",
+					"generated certificate expiring on",
+					"wrote key:", "e2e-test.key",
+					"wrote certificate:", "e2e-test.crt",
+					"Successfully added e2e-test.crt to named store e2e-test of type ca",
+					"e2e-test: added to the key list",
+				)
+
+			notation.Exec("cert", "cleanup-test", "e2e-test", "-y").
+				MatchKeyWords(
+					"Successfully deleted e2e-test.crt from trust store e2e-test of type ca",
+					"Successfully removed key e2e-test from the key list",
+					"Successfully deleted key file:", "e2e-test.key",
+					"Successfully deleted certificate file:", "e2e-test.crt",
+					"Cleanup completed successfully",
+				)
+
+			notation.Exec("cert", "generate-test", "e2e-test").
+				MatchKeyWords(
+					"generating RSA Key with 2048 bits",
+					"generated certificate expiring on",
+					"wrote key:", "e2e-test.key",
+					"wrote certificate:", "e2e-test.crt",
+					"Successfully added e2e-test.crt to named store e2e-test of type ca",
+					"e2e-test: added to the key list",
+				)
+
+			notation.Exec("cert", "cleanup-test", "e2e-test", "-y").
+				MatchKeyWords(
+					"Successfully deleted e2e-test.crt from trust store e2e-test of type ca",
+					"Successfully removed key e2e-test from the key list",
+					"Successfully deleted key file:", "e2e-test.key",
+					"Successfully deleted certificate file:", "e2e-test.crt",
+					"Cleanup completed successfully",
+				)
+		})
+	})
+
+	It("cleanup test with key never generated", func() {
+		Host(BaseOptions(), func(notation *utils.ExecOpts, artifact *Artifact, vhost *utils.VirtualHost) {
+			localKeyPath := vhost.AbsolutePath(NotationDirName, LocalKeysDirName, "e2e-test.key")
+			localCertPath := vhost.AbsolutePath(NotationDirName, LocalKeysDirName, "e2e-test.crt")
+			notation.Exec("cert", "cleanup-test", "e2e-test", "-y").
+				MatchKeyWords(
+					"Certificate e2e-test.crt does not exist in trust store e2e-test of type ca",
+					"Key e2e-test does not exist in the key list",
+					fmt.Sprintf("Key file %s does not exist", localKeyPath),
+					fmt.Sprintf("Certificate file %s does not exist", localCertPath),
+					"Cleanup completed successfully",
+				)
+		})
+	})
+
+	It("cleanup test with certificate not in trust store", func() {
+		Host(BaseOptions(), func(notation *utils.ExecOpts, artifact *Artifact, vhost *utils.VirtualHost) {
+			notation.Exec("cert", "generate-test", "e2e-test")
+
+			notation.Exec("cert", "delete", "--type", "ca", "--store", "e2e-test", "e2e-test.crt", "-y").
+				MatchKeyWords(
+					"Successfully deleted e2e-test.crt from trust store e2e-test of type ca",
+				)
+
+			notation.Exec("cert", "cleanup-test", "e2e-test", "-y").
+				MatchKeyWords(
+					"Certificate e2e-test.crt does not exist in trust store e2e-test of type ca",
+					"Successfully removed key e2e-test from the key list",
+					"Successfully deleted key file:", "e2e-test.key",
+					"Successfully deleted certificate file:", "e2e-test.crt",
+					"Cleanup completed successfully",
+				)
+		})
+	})
+
+	It("cleanup test without local key file", func() {
+		Host(BaseOptions(), func(notation *utils.ExecOpts, artifact *Artifact, vhost *utils.VirtualHost) {
+			notation.Exec("cert", "generate-test", "e2e-test")
+
+			localKeyPath := vhost.AbsolutePath(NotationDirName, LocalKeysDirName, "e2e-test.key")
+			if err := os.Remove(localKeyPath); err != nil {
+				Fail(err.Error())
+			}
+			notation.Exec("cert", "cleanup-test", "e2e-test", "-y").
+				MatchKeyWords(
+					"Successfully deleted e2e-test.crt from trust store e2e-test of type ca",
+					"Successfully removed key e2e-test from the key list",
+					fmt.Sprintf("Key file %s does not exist", localKeyPath),
+					"Successfully deleted certificate file:", "e2e-test.crt",
+					"Cleanup completed successfully",
+				)
+		})
+	})
+
+	It("cleanup test without local certificate file", func() {
+		Host(BaseOptions(), func(notation *utils.ExecOpts, artifact *Artifact, vhost *utils.VirtualHost) {
+			notation.Exec("cert", "generate-test", "e2e-test")
+
+			localCertPath := vhost.AbsolutePath(NotationDirName, LocalKeysDirName, "e2e-test.crt")
+			if err := os.Remove(localCertPath); err != nil {
+				Fail(err.Error())
+			}
+			notation.Exec("cert", "cleanup-test", "e2e-test", "-y").
+				MatchKeyWords(
+					"Successfully deleted e2e-test.crt from trust store e2e-test of type ca",
+					"Successfully removed key e2e-test from the key list",
+					"Successfully deleted key file:", "e2e-test.key",
+					fmt.Sprintf("Certificate file %s does not exist", localCertPath),
+					"Cleanup completed successfully",
+				)
+		})
+	})
+
+	It("cleanup test missing certificate common name", func() {
+		Host(BaseOptions(), func(notation *utils.ExecOpts, artifact *Artifact, vhost *utils.VirtualHost) {
+			notation.ExpectFailure().Exec("cert", "cleanup-test").
+				MatchErrKeyWords(
+					"missing certificate common name",
+				)
+		})
+	})
+
+	It("cleanup test with empty certificate common name", func() {
+		Host(BaseOptions(), func(notation *utils.ExecOpts, artifact *Artifact, vhost *utils.VirtualHost) {
+			notation.ExpectFailure().Exec("cert", "cleanup-test", "").
+				MatchErrKeyWords(
+					"certificate common name must follow [a-zA-Z0-9_.-]+ format",
+				)
+		})
+	})
+
+	It("cleanup test failed at deleting certificate from trust store", func() {
+		Host(BaseOptions(), func(notation *utils.ExecOpts, artifact *Artifact, vhost *utils.VirtualHost) {
+			notation.Exec("cert", "generate-test", "e2e-test")
+
+			certPath := vhost.AbsolutePath(NotationDirName, TrustStoreDirName, "x509", TrustStoreTypeCA, "e2e-test")
+			os.Chmod(certPath, 0000)
+			defer os.Chmod(certPath, 0755)
+
+			notation.ExpectFailure().Exec("cert", "cleanup-test", "e2e-test", "-y").
+				MatchErrKeyWords(
+					"failed to delete certificate e2e-test.crt from trust store e2e-test of type ca",
+					"permission denied",
+				)
+		})
+	})
+
+	It("cleanup test failed at removing key from signingkeys.json", func() {
+		Host(BaseOptions(), func(notation *utils.ExecOpts, artifact *Artifact, vhost *utils.VirtualHost) {
+			notation.Exec("cert", "generate-test", "e2e-test")
+
+			signingKeyPath := vhost.AbsolutePath(NotationDirName, SigningKeysFileName)
+			os.Chmod(signingKeyPath, 0000)
+			defer os.Chmod(signingKeyPath, 0600)
+
+			notation.ExpectFailure().Exec("cert", "cleanup-test", "e2e-test", "-y").
+				MatchErrKeyWords(
+					"failed to remove key e2e-test from the key list",
+					"permission denied",
+				)
+		})
+	})
+
+	It("cleanup test failed at deleting local key file", func() {
+		Host(BaseOptions(), func(notation *utils.ExecOpts, artifact *Artifact, vhost *utils.VirtualHost) {
+			notation.Exec("cert", "generate-test", "e2e-test")
+
+			localKeysDir := vhost.AbsolutePath(NotationDirName, LocalKeysDirName)
+			os.Chmod(localKeysDir, 0000)
+			defer os.Chmod(localKeysDir, 0755)
+
+			notation.ExpectFailure().Exec("cert", "cleanup-test", "e2e-test", "-y").
+				MatchErrKeyWords(
+					fmt.Sprintf("failed to delete key file %s", filepath.Join(localKeysDir, "e2e-test.key")),
+					"permission denied",
+				)
+		})
+	})
 })
diff --git a/test/e2e/suite/scenario/quickstart.go b/test/e2e/suite/scenario/quickstart.go
index cec27a1..76347ea 100644
--- a/test/e2e/suite/scenario/quickstart.go
+++ b/test/e2e/suite/scenario/quickstart.go
@@ -53,7 +53,7 @@ var _ = Describe("notation quickstart E2E test", Ordered, func() {
 			MatchKeyWords(
 				"Successfully added wabbit-networks.io.crt",
 				"wabbit-networks.io: added to the key list",
-				"wabbit-networks.io: mark as default signing key")
+				"wabbit-networks.io: marked as default signing key")
 
 		notation.Exec("key", "ls").
 			MatchKeyWords(